Rebase to master@68888117

Change-Id: Iaa8f16c2bb3a85af73de5884916a53695ef9ef86
Exempt-From-Owner-Approval: rebase
BUG: 160122832
Merged-In: I7ae611add53627d2c00beeeac829fa31069d5594
diff --git a/.github/lock.yml b/.github/lock.yml
new file mode 100644
index 0000000..119e484
--- /dev/null
+++ b/.github/lock.yml
@@ -0,0 +1,2 @@
+daysUntilLock: 90
+lockComment: false
diff --git a/.travis.yml b/.travis.yml
index e3b59ff..952f2ab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -49,7 +49,6 @@
   # 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
diff --git a/Android.bp b/Android.bp
index 67278a9..3b61d08 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,10 +17,14 @@
 java_library_host {
     name: "grpc-java",
     static_libs: [
+        "grpc-java-auth",
+        "grpc-java-context",
         "grpc-java-core",
-	"grpc-java-context",
-	"grpc-java-protobuf",
-	"grpc-java-stub",
-	"grpc-java-netty-shaded",
+        "grpc-java-core-inprocess",
+        "grpc-java-core-internal",
+        "grpc-java-core-util",
+        "grpc-java-protobuf",
+        "grpc-java-protobuf-lite",
+        "grpc-java-stub",
     ]
 }
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/README.md b/README.md
index bbede99..b9788be 100644
--- a/README.md
+++ b/README.md
@@ -30,8 +30,8 @@
 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)
+The [examples](https://github.com/grpc/grpc-java/tree/v1.16.1/examples) and the
+[Android example](https://github.com/grpc/grpc-java/tree/v1.16.1/examples/android)
 are standalone projects that showcase the usage of gRPC.
 
 Download
@@ -42,37 +42,37 @@
 <dependency>
   <groupId>io.grpc</groupId>
   <artifactId>grpc-netty-shaded</artifactId>
-  <version>1.15.0</version>
+  <version>1.16.1</version>
 </dependency>
 <dependency>
   <groupId>io.grpc</groupId>
   <artifactId>grpc-protobuf</artifactId>
-  <version>1.15.0</version>
+  <version>1.16.1</version>
 </dependency>
 <dependency>
   <groupId>io.grpc</groupId>
   <artifactId>grpc-stub</artifactId>
-  <version>1.15.0</version>
+  <version>1.16.1</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'
+compile 'io.grpc:grpc-netty-shaded:1.16.1'
+compile 'io.grpc:grpc-protobuf:1.16.1'
+compile 'io.grpc:grpc-stub:1.16.1'
 ```
 
 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'
+compile 'io.grpc:grpc-okhttp:1.16.1'
+compile 'io.grpc:grpc-protobuf-lite:1.16.1'
+compile 'io.grpc:grpc-stub:1.16.1'
 ```
 
 [the JARs]:
-https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.15.0
+https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.16.1
 
 Development snapshots are available in [Sonatypes's snapshot
 repository](https://oss.sonatype.org/content/repositories/snapshots/).
@@ -104,7 +104,7 @@
       <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>
+        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}</pluginArtifact>
       </configuration>
       <executions>
         <execution>
@@ -141,7 +141,7 @@
   }
   plugins {
     grpc {
-      artifact = 'io.grpc:protoc-gen-grpc-java:1.15.0'
+      artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1'
     }
   }
   generateProtoTasks {
diff --git a/RELEASING.md b/RELEASING.md
index 9fee632..1306ee5 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -113,7 +113,7 @@
    ```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
+   $ sed -i s/-SNAPSHOT// compiler/src/test{,Lite,Nano}/golden/Test{,Deprecated}Service.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"
@@ -125,7 +125,8 @@
    # 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
+   $ sed -i s/$MAJOR.$MINOR.$PATCH/$MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT/ \
+     compiler/src/test{,Lite,Nano}/golden/Test{,Deprecated}Service.java.txt
    $ ./gradlew build
    $ git commit -a -m "Bump version to $MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT"
    ```
diff --git a/SECURITY.md b/SECURITY.md
index 296aad2..891e00c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -373,7 +373,8 @@
 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
+1.14.x-1.15.x      | 4.1.27.Final          | 2.0.12.Final
+1.16.x-            | 4.1.30.Final          | 2.0.17.Final
 
 _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
 
diff --git a/alts/build.gradle b/alts/build.gradle
index 5a8c9e3..e22e055 100644
--- a/alts/build.gradle
+++ b/alts/build.gradle
@@ -27,7 +27,7 @@
             libraries.lang,
             libraries.protobuf
     compile (libraries.google_auth_oauth2_http) {
-        // prefer 3.0.0 from libraries instead of 1.3.9
+        // prefer 3.0.2 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'
diff --git a/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java
index ce24861..af81325 100644
--- a/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java
+++ b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java
@@ -16,9 +16,8 @@
 
 package io.grpc.alts;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 import io.grpc.CallOptions;
 import io.grpc.Channel;
 import io.grpc.ClientCall;
@@ -38,14 +37,9 @@
 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;
@@ -60,13 +54,14 @@
 
   private static final Logger logger = Logger.getLogger(AltsChannelBuilder.class.getName());
   private final NettyChannelBuilder delegate;
-  private final AltsClientOptions.Builder handshakerOptionsBuilder =
-      new AltsClientOptions.Builder();
+  private final ImmutableList.Builder<String> targetServiceAccountsBuilder =
+      ImmutableList.builder();
   private ObjectPool<ManagedChannel> handshakerChannelPool =
       SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL);
-  private TcpfFactory tcpfFactoryForTest;
   private boolean enableUntrustedAlts;
 
+  private AltsProtocolNegotiator negotiatorForTest;
+
   /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
   public static final AltsChannelBuilder forTarget(String target) {
     return new AltsChannelBuilder(target);
@@ -83,14 +78,8 @@
             .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;
+    InternalNettyChannelBuilder.setProtocolNegotiatorFactory(
+        delegate(), new ProtocolNegotiatorFactory());
   }
 
   /**
@@ -98,7 +87,7 @@
    * service account in the handshaker result. Otherwise, the handshake fails.
    */
   public AltsChannelBuilder addTargetServiceAccount(String targetServiceAccount) {
-    handshakerOptionsBuilder.addTargetServiceAccount(targetServiceAccount);
+    targetServiceAccountsBuilder.add(targetServiceAccount);
     return this;
   }
 
@@ -140,84 +129,41 @@
       }
     }
 
-    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;
+  AltsProtocolNegotiator getProtocolNegotiatorForTest() {
+    return negotiatorForTest;
   }
 
-  @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;
-    }
+  private final class ProtocolNegotiatorFactory
+      implements InternalNettyChannelBuilder.ProtocolNegotiatorFactory {
 
     @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;
-        }
-      };
+    public AltsProtocolNegotiator buildProtocolNegotiator() {
+      final ImmutableList<String> targetServiceAccounts = targetServiceAccountsBuilder.build();
+      TsiHandshakerFactory altsHandshakerFactory =
+          new TsiHandshakerFactory() {
+            @Override
+            public TsiHandshaker newHandshaker(String authority) {
+              // 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.
+              AltsClientOptions handshakerOptions =
+                  new AltsClientOptions.Builder()
+                      .setRpcProtocolVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions())
+                      .setTargetServiceAccounts(targetServiceAccounts)
+                      .setTargetName(authority)
+                      .build();
+              return AltsTsiHandshaker.newClient(
+                  HandshakerServiceGrpc.newStub(handshakerChannelPool.getObject()),
+                  handshakerOptions);
+            }
+          };
+      return negotiatorForTest =
+          AltsProtocolNegotiator.createClientNegotiator(altsHandshakerFactory);
     }
   }
 
diff --git a/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java
index a264431..ae564df 100644
--- a/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java
+++ b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java
@@ -197,10 +197,10 @@
     }
 
     delegate.protocolNegotiator(
-        AltsProtocolNegotiator.create(
+        AltsProtocolNegotiator.createServerNegotiator(
             new TsiHandshakerFactory() {
               @Override
-              public TsiHandshaker newHandshaker() {
+              public TsiHandshaker newHandshaker(String authority) {
                 // 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.
diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java
index 619ce04..8a5ece6 100644
--- a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java
+++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java
@@ -16,8 +16,6 @@
 
 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;
@@ -39,17 +37,12 @@
 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;
 
@@ -61,37 +54,12 @@
     extends ForwardingChannelBuilder<GoogleDefaultChannelBuilder> {
 
   private final NettyChannelBuilder delegate;
-  private final TcpfFactory tcpfFactory;
+  private GoogleDefaultProtocolNegotiator negotiatorForTest;
 
   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);
+    InternalNettyChannelBuilder.setProtocolNegotiatorFactory(
+        delegate(), new ProtocolNegotiatorFactory());
   }
 
   /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
@@ -125,48 +93,40 @@
   }
 
   @VisibleForTesting
-  TransportCreationParamsFilterFactory getTcpfFactoryForTest() {
-    return tcpfFactory;
+  GoogleDefaultProtocolNegotiator getProtocolNegotiatorForTest() {
+    return negotiatorForTest;
   }
 
-  private static final class TcpfFactory implements TransportCreationParamsFilterFactory {
-    private final GoogleDefaultProtocolNegotiator negotiator;
-
-    private TcpfFactory(GoogleDefaultProtocolNegotiator negotiator) {
-      this.negotiator = negotiator;
-    }
-
+  private final class ProtocolNegotiatorFactory
+      implements InternalNettyChannelBuilder.ProtocolNegotiatorFactory {
     @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;
-        }
-      };
+    public GoogleDefaultProtocolNegotiator buildProtocolNegotiator() {
+      TsiHandshakerFactory altsHandshakerFactory =
+          new TsiHandshakerFactory() {
+            @Override
+            public TsiHandshaker newHandshaker(String authority) {
+              // 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);
+              AltsClientOptions handshakerOptions =
+                  new AltsClientOptions.Builder()
+                      .setRpcProtocolVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions())
+                      .setTargetName(authority)
+                      .build();
+              return AltsTsiHandshaker.newClient(
+                  HandshakerServiceGrpc.newStub(channel), handshakerOptions);
+            }
+          };
+      SslContext sslContext;
+      try {
+        sslContext = GrpcSslContexts.forClient().build();
+      } catch (SSLException ex) {
+        throw new RuntimeException(ex);
+      }
+      return negotiatorForTest =
+          new GoogleDefaultProtocolNegotiator(altsHandshakerFactory, sslContext);
     }
   }
 
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java b/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java
index e0e506a..e70e7d9 100644
--- a/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java
@@ -16,42 +16,40 @@
 
 package io.grpc.alts.internal;
 
+import com.google.common.collect.ImmutableList;
 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.
+
+  // targetName is the server service account name for secure name checking.
   @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 final ImmutableList<String> targetServiceAccounts;
 
   private AltsClientOptions(Builder builder) {
     super(builder.rpcProtocolVersions);
     targetName = builder.targetName;
-    targetServiceAccounts =
-        Collections.unmodifiableList(new ArrayList<>(builder.targetServiceAccounts));
+    targetServiceAccounts = builder.targetServiceAccounts;
   }
 
   public String getTargetName() {
     return targetName;
   }
 
-  public List<String> getTargetServiceAccounts() {
+  public ImmutableList<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<>();
+    private ImmutableList<String> targetServiceAccounts = ImmutableList.of();
 
     public Builder setTargetName(String targetName) {
       this.targetName = targetName;
@@ -63,8 +61,8 @@
       return this;
     }
 
-    public Builder addTargetServiceAccount(String targetServiceAccount) {
-      targetServiceAccounts.add(targetServiceAccount);
+    public Builder setTargetServiceAccounts(ImmutableList<String> targetServiceAccounts) {
+      this.targetServiceAccounts = targetServiceAccounts;
       return this;
     }
 
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java
index f146dbc..300fd78 100644
--- a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java
@@ -36,34 +36,55 @@
 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}.
+ * A 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 =
+  @Grpc.TransportAttr
+  public static final Attributes.Key<TsiPeer> TSI_PEER_KEY = Attributes.Key.create("TSI_PEER");
+  @Grpc.TransportAttr
+  public 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) {
+  /** Creates a negotiator used for ALTS client. */
+  public static AltsProtocolNegotiator createClientNegotiator(
+      final TsiHandshakerFactory handshakerFactory) {
     return new AltsProtocolNegotiator() {
       @Override
       public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
         return new BufferUntilAltsNegotiatedHandler(
             grpcHandler,
-            new TsiHandshakeHandler(new NettyTsiHandshaker(handshakerFactory.newHandshaker())),
+            new TsiHandshakeHandler(
+                new NettyTsiHandshaker(
+                    handshakerFactory.newHandshaker(grpcHandler.getAuthority()))),
             new TsiFrameHandler());
       }
+
+      @Override
+      public void close() {
+        // TODO(jiangtaoli2016): release resources
+      }
+    };
+  }
+
+  /** Creates a negotiator used for ALTS server. */
+  public static AltsProtocolNegotiator createServerNegotiator(
+      final TsiHandshakerFactory handshakerFactory) {
+    return new AltsProtocolNegotiator() {
+      @Override
+      public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
+        return new BufferUntilAltsNegotiatedHandler(
+            grpcHandler,
+            new TsiHandshakeHandler(new NettyTsiHandshaker(handshakerFactory.newHandshaker(null))),
+            new TsiFrameHandler());
+      }
+
+      @Override
+      public void close() {
+        // TODO(jiangtaoli2016): release resources
+      }
     };
   }
 
@@ -124,6 +145,7 @@
                     .set(TSI_PEER_KEY, altsEvt.peer())
                     .set(ALTS_CONTEXT_KEY, altsContext)
                     .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                    .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
                     .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
                     .build(),
                 new Security(new OtherSecurity("alts", Any.pack(altsContext.context))));
diff --git a/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java
index 8428894..b084534 100644
--- a/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java
+++ b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java
@@ -29,7 +29,7 @@
   private final ProtocolNegotiator tlsProtocolNegotiator;
 
   public GoogleDefaultProtocolNegotiator(TsiHandshakerFactory altsFactory, SslContext sslContext) {
-    altsProtocolNegotiator = AltsProtocolNegotiator.create(altsFactory);
+    altsProtocolNegotiator = AltsProtocolNegotiator.createClientNegotiator(altsFactory);
     tlsProtocolNegotiator = ProtocolNegotiators.tls(sslContext);
   }
 
@@ -49,4 +49,10 @@
       return tlsProtocolNegotiator.newHandler(grpcHandler);
     }
   }
+
+  @Override
+  public void close() {
+    altsProtocolNegotiator.close();
+    tlsProtocolNegotiator.close();
+  }
 }
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java
index 58e3726..996bd00 100644
--- a/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java
@@ -16,9 +16,11 @@
 
 package io.grpc.alts.internal;
 
+import javax.annotation.Nullable;
+
 /** Factory that manufactures instances of {@link TsiHandshaker}. */
 public interface TsiHandshakerFactory {
 
   /** Creates a new handshaker. */
-  TsiHandshaker newHandshaker();
+  TsiHandshaker newHandshaker(@Nullable String authority);
 }
diff --git a/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java
index 747992a..e8cc538 100644
--- a/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java
+++ b/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java
@@ -18,12 +18,8 @@
 
 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;
@@ -36,32 +32,13 @@
     AltsChannelBuilder builder =
         AltsChannelBuilder.forTarget("localhost:8080").enableUntrustedAltsForTesting();
 
-    TransportCreationParamsFilterFactory tcpfFactory = builder.getTcpfFactoryForTest();
-    AltsClientOptions altsClientOptions = builder.getAltsClientOptionsForTest();
-
-    assertThat(tcpfFactory).isNull();
-    assertThat(altsClientOptions).isNull();
+    ProtocolNegotiator protocolNegotiator = builder.getProtocolNegotiatorForTest();
+    assertThat(protocolNegotiator).isNull();
 
     builder.build();
 
-    tcpfFactory = builder.getTcpfFactoryForTest();
-    altsClientOptions = builder.getAltsClientOptionsForTest();
-
-    assertThat(tcpfFactory).isNotNull();
-    ProtocolNegotiator protocolNegotiator =
-        tcpfFactory
-            .create(new InetSocketAddress(8080), "fakeAuthority", "fakeUserAgent", null)
-            .getProtocolNegotiator();
+    protocolNegotiator = builder.getProtocolNegotiatorForTest();
+    assertThat(protocolNegotiator).isNotNull();
     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/GoogleDefaultChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java
index b681c73..d9bba54 100644
--- a/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java
+++ b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java
@@ -19,9 +19,7 @@
 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;
@@ -32,13 +30,9 @@
   @Test
   public void buildsNettyChannel() throws Exception {
     GoogleDefaultChannelBuilder builder = GoogleDefaultChannelBuilder.forTarget("localhost:8080");
+    builder.build();
 
-    TransportCreationParamsFilterFactory tcpfFactory = builder.getTcpfFactoryForTest();
-    assertThat(tcpfFactory).isNotNull();
-    ProtocolNegotiator protocolNegotiator =
-        tcpfFactory
-            .create(new InetSocketAddress(8080), "fakeAuthority", "fakeUserAgent", null)
-            .getProtocolNegotiator();
+    ProtocolNegotiator protocolNegotiator = builder.getProtocolNegotiatorForTest();
     assertThat(protocolNegotiator).isInstanceOf(GoogleDefaultProtocolNegotiator.class);
   }
 }
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java
index cdb44c9..5a7cf84 100644
--- a/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableList;
 import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,12 +40,12 @@
             .setMinRpcVersion(
                 RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
             .build();
+    ImmutableList<String> serviceAccounts = ImmutableList.of(serviceAccount1, serviceAccount2);
 
     AltsClientOptions options =
         new AltsClientOptions.Builder()
             .setTargetName(targetName)
-            .addTargetServiceAccount(serviceAccount1)
-            .addTargetServiceAccount(serviceAccount2)
+            .setTargetServiceAccounts(serviceAccounts)
             .setRpcProtocolVersions(rpcVersions)
             .build();
 
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java
index d5cbecf..41923b5 100644
--- a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.common.collect.ImmutableList;
 import com.google.protobuf.ByteString;
 import io.grpc.alts.internal.Handshaker.HandshakeProtocol;
 import io.grpc.alts.internal.Handshaker.HandshakerReq;
@@ -61,7 +62,7 @@
     clientOptions =
         new AltsClientOptions.Builder()
             .setTargetName(TEST_TARGET_NAME)
-            .addTargetServiceAccount(TEST_TARGET_SERVICE_ACCOUNT)
+            .setTargetServiceAccounts(ImmutableList.of(TEST_TARGET_SERVICE_ACCOUNT))
             .build();
     handshaker = new AltsHandshakerClient(mockStub, clientOptions);
   }
@@ -249,7 +250,7 @@
     clientOptions =
         new AltsClientOptions.Builder()
             .setTargetName(TEST_TARGET_NAME)
-            .addTargetServiceAccount(TEST_TARGET_SERVICE_ACCOUNT)
+            .setTargetServiceAccounts(ImmutableList.of(TEST_TARGET_SERVICE_ACCOUNT))
             .setRpcProtocolVersions(rpcVersions)
             .build();
     handshaker = new AltsHandshakerClient(mockStub, clientOptions);
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java
index 936c298..1670a4c 100644
--- a/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java
@@ -76,6 +76,7 @@
 /** Tests for {@link AltsProtocolNegotiator}. */
 @RunWith(JUnit4.class)
 public class AltsProtocolNegotiatorTest {
+
   private final CapturingGrpcHttp2ConnectionHandler grpcHandler = capturingGrpcHandler();
 
   private final List<ReferenceCounted> references = new ArrayList<>();
@@ -133,8 +134,8 @@
     TsiHandshakerFactory handshakerFactory =
         new DelegatingTsiHandshakerFactory(FakeTsiHandshaker.clientHandshakerFactory()) {
           @Override
-          public TsiHandshaker newHandshaker() {
-            return new DelegatingTsiHandshaker(super.newHandshaker()) {
+          public TsiHandshaker newHandshaker(String authority) {
+            return new DelegatingTsiHandshaker(super.newHandshaker(authority)) {
               @Override
               public TsiPeer extractPeer() throws GeneralSecurityException {
                 return mockedTsiPeer;
@@ -147,7 +148,8 @@
             };
           }
         };
-    handler = AltsProtocolNegotiator.create(handshakerFactory).newHandler(grpcHandler);
+    handler =
+        AltsProtocolNegotiator.createServerNegotiator(handshakerFactory).newHandler(grpcHandler);
     channel = new EmbeddedChannel(uncaughtExceptionHandler, handler, userEventHandler);
   }
 
@@ -339,12 +341,14 @@
   public void peerPropagated() throws Exception {
     doHandshake();
 
-    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.getTsiPeerAttributeKey()))
+    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.TSI_PEER_KEY))
         .isEqualTo(mockedTsiPeer);
-    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.getAltsAuthContextAttributeKey()))
+    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.ALTS_CONTEXT_KEY))
         .isEqualTo(mockedAltsContext);
     assertThat(grpcHandler.attrs.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString())
         .isEqualTo("embedded");
+    assertThat(grpcHandler.attrs.get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR).toString())
+        .isEqualTo("embedded");
     assertThat(grpcHandler.attrs.get(CallCredentials.ATTR_SECURITY_LEVEL))
         .isEqualTo(SecurityLevel.PRIVACY_AND_INTEGRITY);
   }
@@ -394,6 +398,7 @@
   }
 
   private final class CapturingGrpcHttp2ConnectionHandler extends GrpcHttp2ConnectionHandler {
+
     private Attributes attrs;
 
     private CapturingGrpcHttp2ConnectionHandler(
@@ -421,8 +426,8 @@
     }
 
     @Override
-    public TsiHandshaker newHandshaker() {
-      return delegate.newHandshaker();
+    public TsiHandshaker newHandshaker(String authority) {
+      return delegate.newHandshaker(authority);
     }
   }
 
@@ -477,6 +482,7 @@
   }
 
   private static class InterceptingProtector implements TsiFrameProtector {
+
     private final TsiFrameProtector delegate;
     final AtomicInteger flushes = new AtomicInteger();
 
diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java
index ea728e3..ebec896 100644
--- a/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java
+++ b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java
@@ -37,7 +37,7 @@
   private static final TsiHandshakerFactory clientHandshakerFactory =
       new TsiHandshakerFactory() {
         @Override
-        public TsiHandshaker newHandshaker() {
+        public TsiHandshaker newHandshaker(String authority) {
           return new FakeTsiHandshaker(true);
         }
       };
@@ -45,7 +45,7 @@
   private static final TsiHandshakerFactory serverHandshakerFactory =
       new TsiHandshakerFactory() {
         @Override
-        public TsiHandshaker newHandshaker() {
+        public TsiHandshaker newHandshaker(String authority) {
           return new FakeTsiHandshaker(false);
         }
       };
@@ -83,11 +83,11 @@
   }
 
   public static TsiHandshaker newFakeHandshakerClient() {
-    return clientHandshakerFactory.newHandshaker();
+    return clientHandshakerFactory.newHandshaker(null);
   }
 
   public static TsiHandshaker newFakeHandshakerServer() {
-    return serverHandshakerFactory.newHandshaker();
+    return serverHandshakerFactory.newHandshaker(null);
   }
 
   protected FakeTsiHandshaker(boolean isClient) {
diff --git a/android-interop-testing/app/build.gradle b/android-interop-testing/app/build.gradle
index 37238ba..e704dfe 100644
--- a/android-interop-testing/app/build.gradle
+++ b/android-interop-testing/app/build.gradle
@@ -42,7 +42,7 @@
     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
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' // CURRENT_GRPC_VERSION
         }
     }
     generateProtoTasks {
@@ -71,11 +71,11 @@
     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
+    implementation 'io.grpc:grpc-auth:1.16.1' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-protobuf-lite:1.16.1' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-stub:1.16.1' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-testing:1.16.1' // CURRENT_GRPC_VERSION
 
     // workaround for https://github.com/google/protobuf/issues/1889
     protobuf 'com.google.protobuf:protobuf-java:3.0.2'
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
index 0d88341..47f6fc7 100644
--- 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
@@ -127,7 +127,7 @@
     new InteropTask(
             listener,
             TesterOkHttpChannelBuilder.build(host, port, serverHostOverride, useTls, testCa),
-            new ArrayList<>(),
+            new ArrayList<ClientInterceptor>(),
             testCase)
         .execute();
     String result = resultFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle
index 929c7a6..d4fce14 100644
--- a/android-interop-testing/build.gradle
+++ b/android-interop-testing/build.gradle
@@ -2,8 +2,8 @@
 
 buildscript {
     repositories {
-        jcenter()
         google()
+        jcenter()
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:3.1.2'
@@ -16,9 +16,9 @@
 
 allprojects {
     repositories {
+        google()
         mavenLocal()
         jcenter()
-        google()
     }
 }
 
diff --git a/android/build.gradle b/android/build.gradle
index d3303c4..7aad597 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'com.android.library'
 
 group = "io.grpc"
-version = "1.16.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+version = "1.16.1" // CURRENT_GRPC_VERSION
 description = 'gRPC: Android'
 
 buildscript {
@@ -41,9 +41,9 @@
 }
 
 dependencies {
-    implementation 'io.grpc:grpc-core:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-core:1.16.1' // CURRENT_GRPC_VERSION
 
-    testImplementation 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    testImplementation 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
     testImplementation 'junit:junit:4.12'
     testImplementation 'org.robolectric:robolectric:3.7.1'
     testImplementation 'com.google.truth:truth:0.39'
diff --git a/annotation-stubs/Android.bp b/annotation-stubs/Android.bp
new file mode 100644
index 0000000..32d177a
--- /dev/null
+++ b/annotation-stubs/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2018 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.
+//
+
+java_library_host {
+    name: "grpc-java-annotation-stubs",
+    srcs: [
+        ":grpc-java-annotation-stubs-srcjar",
+    ],
+}
+
+gensrcs {
+    name: "grpc-java-annotation-stubs-srcjar",
+    tool_files: [
+        "gen_annotations.py",
+    ],
+    tools: [
+        "soong_zip",
+    ],
+    cmd: "$(location gen_annotations.py) $(genDir)/java && " +
+         "$(location soong_zip) -jar -o $(out) -C $(genDir)/java -D $(genDir)/java",
+    srcs: [
+        // A dummy source file since Soong crashes otherwise.
+        "Android.bp",
+    ],
+    output_extension: "srcjar",
+}
diff --git a/annotation-stubs/gen_annotations.py b/annotation-stubs/gen_annotations.py
new file mode 100755
index 0000000..10c33fa
--- /dev/null
+++ b/annotation-stubs/gen_annotations.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2020 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.
+"""Generates stubs for annotations that aren't in the Android source tree."""
+
+import pathlib
+import string
+import sys
+
+_ANNOTATIONS_CLASSES = ['org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement']
+
+_CLASS_TEMPLATE = string.Template("""
+package ${package_name};
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({
+    ElementType.ANNOTATION_TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.FIELD,
+    ElementType.LOCAL_VARIABLE,
+    ElementType.METHOD,
+    ElementType.PACKAGE,
+    ElementType.PARAMETER,
+    ElementType.TYPE,
+    ElementType.TYPE_PARAMETER,
+    ElementType.TYPE_USE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ${class_name} {}
+""")
+
+if __name__ == '__main__':
+  out_dir = pathlib.Path(sys.argv[1])
+
+  for c in _ANNOTATIONS_CLASSES:
+    parts = c.split('.')
+    src_path = out_dir.joinpath(*parts).with_suffix('.java')
+    src_path.parent.mkdir(parents=True)
+    src_path.write_text(
+        _CLASS_TEMPLATE.substitute(
+            package_name='.'.join(parts[:-1]), class_name=parts[-1]))
diff --git a/auth/Android.bp b/auth/Android.bp
new file mode 100644
index 0000000..68d08f4
--- /dev/null
+++ b/auth/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 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.
+//
+
+java_library_host {
+    name: "grpc-java-auth",
+    srcs: [
+        "src/main/java/io/grpc/auth/*.java",
+    ],
+    libs: [
+        "grpc-java-core",
+        "google-auth-library-credentials-0.13.0",
+        "guava",
+        "jsr305",
+    ],
+}
diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
index c258db1..32855e3 100644
--- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
+++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
@@ -22,8 +22,7 @@
 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.CallCredentials2;
 import io.grpc.Metadata;
 import io.grpc.MethodDescriptor;
 import io.grpc.SecurityLevel;
@@ -47,7 +46,7 @@
 /**
  * Wraps {@link Credentials} as a {@link CallCredentials}.
  */
-final class GoogleAuthLibraryCallCredentials implements CallCredentials {
+final class GoogleAuthLibraryCallCredentials extends CallCredentials2 {
   private static final Logger log
       = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName());
   private static final JwtHelper jwtHelper
@@ -88,15 +87,9 @@
   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;
-    }
+  public void applyRequestMetadata(
+      RequestInfo info, Executor appExecutor, final MetadataApplier applier) {
+    SecurityLevel security = info.getSecurityLevel();
     if (requirePrivacy && security != SecurityLevel.PRIVACY_AND_INTEGRITY) {
       applier.fail(Status.UNAUTHENTICATED
           .withDescription("Credentials require channel with PRIVACY_AND_INTEGRITY security level. "
@@ -104,10 +97,10 @@
       return;
     }
 
-    String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority");
+    String authority = checkNotNull(info.getAuthority(), "authority");
     final URI uri;
     try {
-      uri = serviceUri(authority, method);
+      uri = serviceUri(authority, info.getMethodDescriptor());
     } catch (StatusException e) {
       applier.fail(e.getStatus());
       return;
diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
index 438dab0..d296683 100644
--- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
+++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
@@ -39,8 +39,8 @@
 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.CallCredentials2;
+import io.grpc.CallCredentials2.MetadataApplier;
 import io.grpc.Metadata;
 import io.grpc.MethodDescriptor;
 import io.grpc.SecurityLevel;
@@ -105,11 +105,8 @@
       .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 static final String AUTHORITY = "testauthority";
+  private static final SecurityLevel SECURITY_LEVEL = SecurityLevel.PRIVACY_AND_INTEGRITY;
 
   private ArrayList<Runnable> pendingRunnables = new ArrayList<>();
 
@@ -155,7 +152,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
 
     verify(credentials).getRequestMetadata(eq(expectedUri));
     verify(applier).apply(headersCaptor.capture());
@@ -177,7 +174,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
 
     verify(credentials).getRequestMetadata(eq(expectedUri));
     verify(applier).fail(statusCaptor.capture());
@@ -193,7 +190,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
 
     verify(credentials).getRequestMetadata(eq(expectedUri));
     verify(applier).fail(statusCaptor.capture());
@@ -209,7 +206,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
 
     verify(credentials).getRequestMetadata(eq(expectedUri));
     verify(applier).fail(statusCaptor.capture());
@@ -229,7 +226,7 @@
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
     for (int i = 0; i < 3; i++) {
-      callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+      callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
     }
 
     verify(credentials, times(3)).getRequestMetadata(eq(expectedUri));
@@ -255,14 +252,11 @@
         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);
+    callCredentials.applyRequestMetadata(
+        new RequestInfoImpl(SecurityLevel.NONE), executor, applier);
     assertEquals(1, runPendingRunnables());
 
     verify(applier).apply(headersCaptor.capture());
@@ -276,13 +270,11 @@
   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);
+    callCredentials.applyRequestMetadata(
+        new RequestInfoImpl(SecurityLevel.PRIVACY_AND_INTEGRITY), executor, applier);
     runPendingRunnables();
 
     verify(applier).apply(headersCaptor.capture());
@@ -297,33 +289,11 @@
     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);
+    callCredentials.applyRequestMetadata(
+        new RequestInfoImpl(SecurityLevel.INTEGRITY), executor, applier);
     runPendingRunnables();
 
     verify(applier).fail(statusCaptor.capture());
@@ -335,20 +305,12 @@
   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);
+    callCredentials.applyRequestMetadata(
+        new RequestInfoImpl("example.com:443"), 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);
+    callCredentials.applyRequestMetadata(
+        new RequestInfoImpl("example.com:123"), executor, applier);
     verify(credentials).getRequestMetadata(eq(new URI("https://example.com:123/a.service")));
   }
 
@@ -366,7 +328,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
     assertEquals(0, runPendingRunnables());
 
     verify(applier).apply(headersCaptor.capture());
@@ -393,7 +355,7 @@
 
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
     assertEquals(1, runPendingRunnables());
 
     verify(applier).apply(headersCaptor.capture());
@@ -412,7 +374,7 @@
     assertNull(GoogleAuthLibraryCallCredentials.createJwtHelperOrNull(null));
     GoogleAuthLibraryCallCredentials callCredentials =
         new GoogleAuthLibraryCallCredentials(credentials, null);
-    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    callCredentials.applyRequestMetadata(new RequestInfoImpl(), executor, applier);
 
     verify(credentials).getRequestMetadata(eq(expectedUri));
     verify(applier).apply(headersCaptor.capture());
@@ -430,4 +392,46 @@
     }
     return savedPendingRunnables.size();
   }
+
+  private final class RequestInfoImpl extends CallCredentials2.RequestInfo {
+    final String authority;
+    final SecurityLevel securityLevel;
+
+    RequestInfoImpl() {
+      this(AUTHORITY, SECURITY_LEVEL);
+    }
+
+    RequestInfoImpl(SecurityLevel securityLevel) {
+      this(AUTHORITY, securityLevel);
+    }
+
+    RequestInfoImpl(String authority) {
+      this(authority, SECURITY_LEVEL);
+    }
+
+    RequestInfoImpl(String authority, SecurityLevel securityLevel) {
+      this.authority = authority;
+      this.securityLevel = securityLevel;
+    }
+
+    @Override
+    public MethodDescriptor<?, ?> getMethodDescriptor() {
+      return method;
+    }
+
+    @Override
+    public SecurityLevel getSecurityLevel() {
+      return securityLevel;
+    }
+
+    @Override
+    public String getAuthority() {
+      return authority;
+    }
+
+    @Override
+    public Attributes getTransportAttrs() {
+      return Attributes.EMPTY;
+    }
+  }
 }
diff --git a/build.gradle b/build.gradle
index 2297bed..a2c20bb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -53,7 +53,7 @@
     idea.module.inheritOutputDirs = true
 
     group = "io.grpc"
-    version = "1.16.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+    version = "1.16.1" // CURRENT_GRPC_VERSION
 
     sourceCompatibility = 1.7
     targetCompatibility = 1.7
@@ -110,8 +110,8 @@
         protocPluginBaseName = 'protoc-gen-grpc-java'
         javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix"
 
-        nettyVersion = '4.1.27.Final'
-        guavaVersion = '20.0'
+        nettyVersion = '4.1.30.Final'
+        guavaVersion = '26.0-android'
         protobufVersion = '3.5.1'
         protocVersion = '3.5.1-1'
         protobufNanoVersion = '3.0.0-alpha-5'
@@ -201,7 +201,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',
+            jsr305: 'com.google.code.findbugs:jsr305:3.0.2',
             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',
@@ -224,7 +224,7 @@
             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',
+            netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.17.Final',
 
             conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:1.0.1',
             re2j: 'com.google.re2j:re2j:1.2',
@@ -233,7 +233,7 @@
             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',
+            guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
 
             // Benchmark dependencies
             hdrhistogram: 'org.hdrhistogram:HdrHistogram:2.1.10',
diff --git a/buildscripts/make_dependencies.bat b/buildscripts/make_dependencies.bat
index 691520e..6b5f0ac 100644
--- a/buildscripts/make_dependencies.bat
+++ b/buildscripts/make_dependencies.bat
@@ -36,7 +36,7 @@
   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
+msbuild /maxcpucount /p:Configuration=Release /verbosity:minimal libprotoc.vcxproj || exit /b 1
 call extract_includes.bat || exit /b 1
 popd
 goto :eof
diff --git a/buildscripts/make_dependencies.sh b/buildscripts/make_dependencies.sh
index e4aa541..fe9c611 100755
--- a/buildscripts/make_dependencies.sh
+++ b/buildscripts/make_dependencies.sh
@@ -35,7 +35,7 @@
     --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 V=0 -j$NUM_CPU
   make install
   popd
 fi
diff --git a/compiler/Android.bp b/compiler/Android.bp
new file mode 100644
index 0000000..833559a
--- /dev/null
+++ b/compiler/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 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.
+//
+//
+
+cc_binary_host {
+    name: "protoc-gen-grpc-java-plugin",
+    srcs: [
+        "src/java_plugin/cpp/java_generator.cpp",
+        "src/java_plugin/cpp/java_plugin.cpp",
+    ],
+    static_libs: [
+        "libprotoc",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+        "-DFALLTHROUGH_INTENDED=[[fallthrough]]",
+    ],
+}
diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt
index 8cc441b..7698543 100644
--- a/compiler/src/test/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/test/golden/TestDeprecatedService.java.txt
@@ -21,7 +21,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 @java.lang.Deprecated
 public final class TestDeprecatedServiceGrpc {
diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt
index c9e10cf..aebd0b1 100644
--- a/compiler/src/test/golden/TestService.java.txt
+++ b/compiler/src/test/golden/TestService.java.txt
@@ -21,7 +21,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 public final class TestServiceGrpc {
 
diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
index 9601483..0d0490d 100644
--- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
@@ -21,7 +21,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 @java.lang.Deprecated
 public final class TestDeprecatedServiceGrpc {
diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt
index 2adcbdd..c741d6e 100644
--- a/compiler/src/testLite/golden/TestService.java.txt
+++ b/compiler/src/testLite/golden/TestService.java.txt
@@ -21,7 +21,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 public final class TestServiceGrpc {
 
diff --git a/compiler/src/testNano/golden/TestDeprecatedService.java.txt b/compiler/src/testNano/golden/TestDeprecatedService.java.txt
index 6e5bba9..c96a01d 100644
--- a/compiler/src/testNano/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/testNano/golden/TestDeprecatedService.java.txt
@@ -23,7 +23,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 @java.lang.Deprecated
 public final class TestDeprecatedServiceGrpc {
diff --git a/compiler/src/testNano/golden/TestService.java.txt b/compiler/src/testNano/golden/TestService.java.txt
index 35c3332..ee0bd14 100644
--- a/compiler/src/testNano/golden/TestService.java.txt
+++ b/compiler/src/testNano/golden/TestService.java.txt
@@ -23,7 +23,7 @@
  * </pre>
  */
 @javax.annotation.Generated(
-    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    value = "by gRPC proto compiler (version 1.16.1)",
     comments = "Source: grpc/testing/compiler/test.proto")
 public final class TestServiceGrpc {
 
diff --git a/core/Android.bp b/core/Android.bp
index 1d7d87e..ed371b0d 100644
--- a/core/Android.bp
+++ b/core/Android.bp
@@ -12,12 +12,63 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-// TODO: build from source instead
 
-java_import_host {
+java_library_host {
     name: "grpc-java-core",
-    jars: [
-        "grpc-core-1.14.0.jar",
+    srcs: [
+        "src/main/java/io/grpc/*.java",
+    ],
+    java_resource_dirs: [
+        "src/main/resources",
+    ],
+    libs: [
+        "grpc-java-context",
+        "jsr305",
+        "guava",
     ],
 }
 
+java_library_host {
+    name: "grpc-java-core-inprocess",
+    srcs: [
+        "src/main/java/io/grpc/inprocess/*.java",
+    ],
+    libs: [
+        "grpc-java-core",
+        "grpc-java-core-internal",
+        "grpc-java-context",
+        "jsr305",
+        "guava",
+    ],
+}
+
+java_library_host {
+    name: "grpc-java-core-internal",
+    srcs: [
+        "src/main/java/io/grpc/internal/*.java",
+    ],
+    libs: [
+        "grpc-java-annotation-stubs",
+        "grpc-java-core",
+        "grpc-java-context",
+        "jsr305",
+        "gson-prebuilt-jar",
+        "error_prone_annotations",
+        "guava",
+        "opencensus-java-api",
+        "opencensus-java-contrib-grpc-metrics",
+    ],
+}
+
+java_library_host {
+    name: "grpc-java-core-util",
+    srcs: [
+        "src/main/java/io/grpc/util/*.java",
+    ],
+    libs: [
+        "grpc-java-core",
+        "grpc-java-core-internal",
+        "jsr305",
+        "guava",
+    ],
+}
diff --git a/core/build.gradle b/core/build.gradle
index 700592f..4a24290 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -3,12 +3,19 @@
 dependencies {
     compile project(':grpc-context'),
             libraries.gson,
-            libraries.guava,
             libraries.errorprone,
             libraries.jsr305,
             libraries.animalsniffer_annotations
+    compile (libraries.guava) {
+        // prefer 2.2.0 from libraries instead of 2.1.3
+        exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
+        // prefer 3.0.2 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 1.17 from libraries instead of 1.14
+        exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
+    }
     compile (libraries.opencensus_api) {
-        // prefer 3.0.0 from libraries instead of 3.0.1
+        // prefer 3.0.2 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'
@@ -16,7 +23,7 @@
         exclude group: 'io.grpc', module: 'grpc-context'
     }
     compile (libraries.opencensus_contrib_grpc_metrics) {
-        // prefer 3.0.0 from libraries instead of 3.0.1
+        // prefer 3.0.2 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'
diff --git a/core/grpc-core-1.14.0.jar b/core/grpc-core-1.14.0.jar
deleted file mode 100644
index 4057e1a..0000000
--- a/core/grpc-core-1.14.0.jar
+++ /dev/null
Binary files differ
diff --git a/core/src/main/java/io/grpc/Attributes.java b/core/src/main/java/io/grpc/Attributes.java
index 9a13f51..411d11c 100644
--- a/core/src/main/java/io/grpc/Attributes.java
+++ b/core/src/main/java/io/grpc/Attributes.java
@@ -29,6 +29,18 @@
 
 /**
  * An immutable type-safe container of attributes.
+ *
+ * <h3>Annotation semantics</h3>
+ *
+ * <p>As a convention, annotations such as {@link Grpc.TransportAttr} is defined to associate
+ * attribute {@link Key}s and their propagation paths.  The annotation may be applied to a {@code
+ * Key} definition field, a method that returns {@link Attributes}, or a variable of type {@link
+ * Attributes}, to indicate that the annotated {@link Attributes} objects may contain the annotated
+ * {@code Key}.
+ *
+ * <p>Javadoc users may click "USE" on the navigation bars of the annotation's javadoc page to view
+ * references of such annotation.
+ *
  * @since 1.13.0
  */
 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1764")
diff --git a/core/src/main/java/io/grpc/BinaryLog.java b/core/src/main/java/io/grpc/BinaryLog.java
index 5c2d4ab..9d11b63 100644
--- a/core/src/main/java/io/grpc/BinaryLog.java
+++ b/core/src/main/java/io/grpc/BinaryLog.java
@@ -29,21 +29,4 @@
       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/CallCredentials.java b/core/src/main/java/io/grpc/CallCredentials.java
index 7ce6219..03be653 100644
--- a/core/src/main/java/io/grpc/CallCredentials.java
+++ b/core/src/main/java/io/grpc/CallCredentials.java
@@ -40,10 +40,15 @@
    * 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.
+   *
+   * @deprecated transport implementations should use {@code
+   * io.grpc.internal.GrpcAttributes.ATTR_SECURITY_LEVEL} instead.
    */
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  @Grpc.TransportAttr
+  @Deprecated
   public static final Key<SecurityLevel> ATTR_SECURITY_LEVEL =
-      Key.create("io.grpc.CallCredentials.securityLevel");
+      Key.create("io.grpc.internal.GrpcAttributes.securityLevel");
 
   /**
    * The authority string used to authenticate the server. Usually it's the server's host name. It
@@ -52,6 +57,8 @@
    * io.grpc.CallOptions} with increasing precedence.
    */
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  @Grpc.TransportAttr
+  @Deprecated
   public static final Key<String> ATTR_AUTHORITY = Key.create("io.grpc.CallCredentials.authority");
 
   /**
@@ -71,8 +78,11 @@
    *        needs to perform blocking operations.
    * @param applier The outlet of the produced headers. It can be called either before or after this
    *        method returns.
+   *
+   * @deprecated implement {@link CallCredentials2} instead.
    */
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  @Deprecated
   void applyRequestMetadata(
       MethodDescriptor<?, ?> method, Attributes attrs,
       Executor appExecutor, MetadataApplier applier);
@@ -89,6 +99,7 @@
    *
    * <p>Exactly one of its methods must be called to make the RPC proceed.
    */
+  @Deprecated
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
   public interface MetadataApplier {
     /**
@@ -102,4 +113,31 @@
      */
     void fail(Status status);
   }
+
+  /**
+   * The request-related information passed to {@code CallCredentials2.applyRequestMetadata()}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  public abstract static class RequestInfo {
+    /**
+     * The method descriptor of this RPC.
+     */
+    public abstract MethodDescriptor<?, ?> getMethodDescriptor();
+
+    /**
+     * The security level on the transport.
+     */
+    public abstract SecurityLevel getSecurityLevel();
+
+    /**
+     * Returns the authority string used to authenticate the server for this call.
+     */
+    public abstract String getAuthority();
+
+    /**
+     * Returns the transport attributes.
+     */
+    @Grpc.TransportAttr
+    public abstract Attributes getTransportAttrs();
+  }
 }
diff --git a/core/src/main/java/io/grpc/CallCredentials2.java b/core/src/main/java/io/grpc/CallCredentials2.java
new file mode 100644
index 0000000..998df42
--- /dev/null
+++ b/core/src/main/java/io/grpc/CallCredentials2.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;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The new interface for {@link CallCredentials}.
+ *
+ * <p>THIS CLASS NAME IS TEMPORARY and is part of a migration. This class will BE DELETED as it
+ * replaces {@link CallCredentials} in short-term.  THIS CLASS SHOULD ONLY BE REFERENCED BY
+ * IMPLEMENTIONS.  All consumers should still reference {@link CallCredentials}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4901")
+public abstract class CallCredentials2 implements CallCredentials {
+  /**
+   * Pass the credential data to the given {@link CallCredentials.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 requestInfo request-related information
+   * @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")
+  public abstract void applyRequestMetadata(
+      RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier);
+
+  @Override
+  @SuppressWarnings("deprecation")
+  public final void applyRequestMetadata(
+      final MethodDescriptor<?, ?> method, final Attributes attrs,
+      Executor appExecutor, final CallCredentials.MetadataApplier applier) {
+    final String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority");
+    final SecurityLevel securityLevel =
+        firstNonNull(attrs.get(ATTR_SECURITY_LEVEL), SecurityLevel.NONE);
+    RequestInfo requestInfo = new RequestInfo() {
+        @Override
+        public MethodDescriptor<?, ?> getMethodDescriptor() {
+          return method;
+        }
+
+        @Override
+        public SecurityLevel getSecurityLevel() {
+          return securityLevel;
+        }
+
+        @Override
+        public String getAuthority() {
+          return authority;
+        }
+
+        @Override
+        public Attributes getTransportAttrs() {
+          return attrs;
+        }
+      };
+    MetadataApplier applierAdapter = new MetadataApplier() {
+        @Override
+        public void apply(Metadata headers) {
+          applier.apply(headers);
+        }
+
+        @Override
+        public void fail(Status status) {
+          applier.fail(status);
+        }
+      };
+    applyRequestMetadata(requestInfo, appExecutor, applierAdapter);
+  }
+
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  @SuppressWarnings("deprecation")
+  public abstract static class MetadataApplier implements CallCredentials.MetadataApplier {}
+}
diff --git a/core/src/main/java/io/grpc/ClientCall.java b/core/src/main/java/io/grpc/ClientCall.java
index 936cb32..2aa5034 100644
--- a/core/src/main/java/io/grpc/ClientCall.java
+++ b/core/src/main/java/io/grpc/ClientCall.java
@@ -262,12 +262,11 @@
    * 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")
+  @Grpc.TransportAttr
   public Attributes getAttributes() {
     return Attributes.EMPTY;
   }
diff --git a/core/src/main/java/io/grpc/EquivalentAddressGroup.java b/core/src/main/java/io/grpc/EquivalentAddressGroup.java
index 2218a7f..eabd2ea 100644
--- a/core/src/main/java/io/grpc/EquivalentAddressGroup.java
+++ b/core/src/main/java/io/grpc/EquivalentAddressGroup.java
@@ -17,6 +17,9 @@
 package io.grpc;
 
 import com.google.common.base.Preconditions;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -50,7 +53,7 @@
   /**
    * List constructor with {@link Attributes}.
    */
-  public EquivalentAddressGroup(List<SocketAddress> addrs, Attributes attrs) {
+  public EquivalentAddressGroup(List<SocketAddress> addrs, @Attr Attributes attrs) {
     Preconditions.checkArgument(!addrs.isEmpty(), "addrs is empty");
     this.addrs = Collections.unmodifiableList(new ArrayList<>(addrs));
     this.attrs = Preconditions.checkNotNull(attrs, "attrs");
@@ -69,7 +72,7 @@
   /**
    * Singleton constructor with Attributes.
    */
-  public EquivalentAddressGroup(SocketAddress addr, Attributes attrs) {
+  public EquivalentAddressGroup(SocketAddress addr, @Attr Attributes attrs) {
     this(Collections.singletonList(addr), attrs);
   }
 
@@ -83,6 +86,7 @@
   /**
    * Returns the attributes.
    */
+  @Attr
   public Attributes getAttributes() {
     return attrs;
   }
@@ -127,4 +131,13 @@
     }
     return true;
   }
+
+  /**
+   * Annotation for {@link EquivalentAddressGroup}'s attributes. It follows the annotation semantics
+   * defined by {@link Attributes}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
+  @Retention(RetentionPolicy.SOURCE)
+  @Documented
+  public @interface Attr {}
 }
diff --git a/core/src/main/java/io/grpc/Grpc.java b/core/src/main/java/io/grpc/Grpc.java
index aef0726..53ff28b 100644
--- a/core/src/main/java/io/grpc/Grpc.java
+++ b/core/src/main/java/io/grpc/Grpc.java
@@ -16,6 +16,9 @@
 
 package io.grpc;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.SocketAddress;
 import javax.net.ssl.SSLSession;
 
@@ -31,13 +34,32 @@
    * Attribute key for the remote address of a transport.
    */
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1710")
+  @TransportAttr
   public static final Attributes.Key<SocketAddress> TRANSPORT_ATTR_REMOTE_ADDR =
-          Attributes.Key.create("remote-addr");
+      Attributes.Key.create("remote-addr");
+
+  /**
+   * Attribute key for the local address of a transport.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1710")
+  @TransportAttr
+  public static final Attributes.Key<SocketAddress> TRANSPORT_ATTR_LOCAL_ADDR =
+      Attributes.Key.create("local-addr");
 
   /**
    * Attribute key for SSL session of a transport.
    */
   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1710")
+  @TransportAttr
   public static final Attributes.Key<SSLSession> TRANSPORT_ATTR_SSL_SESSION =
-          Attributes.Key.create("ssl-session");
+      Attributes.Key.create("ssl-session");
+
+  /**
+   * Annotation for transport attributes. It follows the annotation semantics defined
+   * by {@link Attributes}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
+  @Retention(RetentionPolicy.SOURCE)
+  @Documented
+  public @interface TransportAttr {}
 }
diff --git a/core/src/main/java/io/grpc/LoadBalancer.java b/core/src/main/java/io/grpc/LoadBalancer.java
index 633bf52..f1cef63 100644
--- a/core/src/main/java/io/grpc/LoadBalancer.java
+++ b/core/src/main/java/io/grpc/LoadBalancer.java
@@ -107,11 +107,12 @@
    * <p>Implementations should not modify the given {@code servers}.
    *
    * @param servers the resolved server addresses, never empty.
-   * @param attributes extra metadata from naming system.
+   * @param attributes extra information from naming system.
    * @since 1.2.0
    */
   public abstract void handleResolvedAddressGroups(
-      List<EquivalentAddressGroup> servers, Attributes attributes);
+      List<EquivalentAddressGroup> servers,
+      @NameResolver.ResolutionResultAttr Attributes attributes);
 
   /**
    * Handles an error from the name resolution system.
@@ -154,6 +155,11 @@
    */
   public abstract void shutdown();
 
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+
   /**
    * 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.
diff --git a/core/src/main/java/io/grpc/NameResolver.java b/core/src/main/java/io/grpc/NameResolver.java
index 60f819e..845dcb0 100644
--- a/core/src/main/java/io/grpc/NameResolver.java
+++ b/core/src/main/java/io/grpc/NameResolver.java
@@ -16,6 +16,9 @@
 
 package io.grpc;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.URI;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -126,6 +129,7 @@
    *
    * @since 1.0.0
    */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
   @ThreadSafe
   public interface Listener {
     /**
@@ -134,10 +138,11 @@
      * <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
+     * @param attributes extra information from naming system.
      * @since 1.3.0
      */
-    void onAddresses(List<EquivalentAddressGroup> servers, Attributes attributes);
+    void onAddresses(
+        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes);
 
     /**
      * Handles an error from the resolver. The listener is responsible for eventually invoking
@@ -148,4 +153,13 @@
      */
     void onError(Status error);
   }
+
+  /**
+   * Annotation for name resolution result attributes. It follows the annotation semantics defined
+   * by {@link Attributes}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
+  @Retention(RetentionPolicy.SOURCE)
+  @Documented
+  public @interface ResolutionResultAttr {}
 }
diff --git a/core/src/main/java/io/grpc/ServerCall.java b/core/src/main/java/io/grpc/ServerCall.java
index 106676f..07711e3 100644
--- a/core/src/main/java/io/grpc/ServerCall.java
+++ b/core/src/main/java/io/grpc/ServerCall.java
@@ -192,11 +192,11 @@
    * 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")
+  @Grpc.TransportAttr
   public Attributes getAttributes() {
     return Attributes.EMPTY;
   }
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
index 7cf1a89..03e2e1a 100644
--- a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
+++ b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
@@ -24,7 +24,6 @@
 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;
@@ -41,6 +40,7 @@
 import io.grpc.internal.ClientStream;
 import io.grpc.internal.ClientStreamListener;
 import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcAttributes;
 import io.grpc.internal.GrpcUtil;
 import io.grpc.internal.ManagedClientTransport;
 import io.grpc.internal.NoopClientStream;
@@ -91,7 +91,7 @@
   @GuardedBy("this")
   private List<ServerStreamTracer.Factory> serverStreamTracerFactories;
   private final Attributes attributes = Attributes.newBuilder()
-      .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+      .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
       .build();
 
   public InProcessTransport(String name, String authority, String userAgent) {
@@ -132,6 +132,7 @@
         synchronized (InProcessTransport.this) {
           Attributes serverTransportAttrs = Attributes.newBuilder()
               .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InProcessSocketAddress(name))
+              .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InProcessSocketAddress(name))
               .build();
           serverStreamAttributes = serverTransportListener.transportReady(serverTransportAttrs);
           clientTransportListener.transportReady();
diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
index 80505bc..a8f778a 100644
--- a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
+++ b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
@@ -16,11 +16,14 @@
 
 package io.grpc.internal;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 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.InternalChannelz.ChannelTrace;
 import io.grpc.LoadBalancer;
 import io.grpc.LoadBalancer.Helper;
 import io.grpc.LoadBalancer.PickResult;
@@ -32,6 +35,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
 final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factory {
@@ -43,11 +47,20 @@
   static final String GRPCLB_LOAD_BALANCER_FACTORY_NAME =
       "io.grpc.grpclb.GrpclbLoadBalancerFactory";
 
-  AutoConfiguredLoadBalancerFactory() {}
+  @Nullable
+  private final ChannelTracer channelTracer;
+  @Nullable
+  private final TimeProvider timeProvider;
+
+  AutoConfiguredLoadBalancerFactory(
+      @Nullable ChannelTracer channelTracer, @Nullable TimeProvider timeProvider) {
+    this.channelTracer = channelTracer;
+    this.timeProvider = timeProvider;
+  }
 
   @Override
   public LoadBalancer newLoadBalancer(Helper helper) {
-    return new AutoConfiguredLoadBalancer(helper);
+    return new AutoConfiguredLoadBalancer(helper, channelTracer, timeProvider);
   }
 
   private static final class NoopLoadBalancer extends LoadBalancer {
@@ -71,11 +84,21 @@
     private final Helper helper;
     private LoadBalancer delegate;
     private LoadBalancer.Factory delegateFactory;
+    @CheckForNull
+    private ChannelTracer channelTracer;
+    @Nullable
+    private final TimeProvider timeProvider;
 
-    AutoConfiguredLoadBalancer(Helper helper) {
+    AutoConfiguredLoadBalancer(
+        Helper helper, @Nullable ChannelTracer channelTracer, @Nullable TimeProvider timeProvider) {
       this.helper = helper;
       delegateFactory = PickFirstBalancerFactory.getInstance();
       delegate = delegateFactory.newLoadBalancer(helper);
+      this.channelTracer = channelTracer;
+      this.timeProvider = timeProvider;
+      if (channelTracer != null) {
+        checkNotNull(timeProvider, "timeProvider");
+      }
     }
 
     //  Must be run inside ChannelExecutor.
@@ -101,7 +124,15 @@
         helper.updateBalancingState(ConnectivityState.CONNECTING, new EmptyPicker());
         delegate.shutdown();
         delegateFactory = newlbf;
+        LoadBalancer old = delegate;
         delegate = delegateFactory.newLoadBalancer(helper);
+        if (channelTracer != null) {
+          channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+              .setDescription("Load balancer changed from " + old + " to " + delegate)
+              .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+              .setTimestampNanos(timeProvider.currentTimeNanos())
+              .build());
+        }
       }
       getDelegate().handleResolvedAddressGroups(servers, attributes);
     }
diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
index df2e0a0..e5141cf 100644
--- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
+++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
@@ -72,6 +72,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     public ClientStream newStream(
         MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
       CallCredentials creds = callOptions.getCredentials();
diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
index 490bd82..dc1b783 100644
--- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
@@ -20,7 +20,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
 import com.google.common.base.Verify;
 import io.grpc.Attributes;
 import io.grpc.EquivalentAddressGroup;
@@ -43,7 +43,6 @@
 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;
@@ -85,27 +84,18 @@
 
   private static final String JNDI_PROPERTY =
       System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi", "true");
+  private static final String JNDI_LOCALHOST_PROPERTY =
+      System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi_localhost", "false");
   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 enableJndiLocalhost = Boolean.parseBoolean(JNDI_LOCALHOST_PROPERTY);
+  @VisibleForTesting
   static boolean enableSrv = Boolean.parseBoolean(JNDI_SRV_PROPERTY);
   @VisibleForTesting
   static boolean enableTxt = Boolean.parseBoolean(JNDI_TXT_PROPERTY);
@@ -130,8 +120,6 @@
   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")
@@ -140,11 +128,10 @@
   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) {
+      Resource<ExecutorService> executorResource,
+      ProxyDetector proxyDetector) {
     // 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;
@@ -167,8 +154,6 @@
       port = nameUri.getPort();
     }
     this.proxyDetector = proxyDetector;
-    this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
-    this.networkAddressCacheTtlNanos = getNetworkAddressCacheTtlNanos();
   }
 
   @Override
@@ -198,13 +183,6 @@
           if (shutdown) {
             return;
           }
-          boolean resourceRefreshRequired = cachedResolutionResults == null
-              || networkAddressCacheTtlNanos == 0
-              || (networkAddressCacheTtlNanos > 0
-                  && stopwatch.elapsed(TimeUnit.NANOSECONDS) > networkAddressCacheTtlNanos);
-          if (!resourceRefreshRequired) {
-            return;
-          }
           savedListener = listener;
           resolving = true;
         }
@@ -229,15 +207,11 @@
           ResolutionResults resolutionResults;
           try {
             ResourceResolver resourceResolver = null;
-            if (enableJndi) {
+            if (shouldUseJndi(enableJndi, enableJndiLocalhost, host)) {
               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));
@@ -284,23 +258,6 @@
       }
     };
 
-  /** 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) {
@@ -368,7 +325,9 @@
       }
     }
     try {
-      if (addressesException != null && balancerAddressesException != null) {
+      if (addressesException != null
+          && (balancerAddressesException != null || balancerAddresses.isEmpty())) {
+        Throwables.throwIfUnchecked(addressesException);
         throw new RuntimeException(addressesException);
       }
     } finally {
@@ -626,4 +585,28 @@
     }
     return localHostname;
   }
+
+  @VisibleForTesting
+  static boolean shouldUseJndi(boolean jndiEnabled, boolean jndiLocalhostEnabled, String target) {
+    if (!jndiEnabled) {
+      return false;
+    }
+    if ("localhost".equalsIgnoreCase(target)) {
+      return jndiLocalhostEnabled;
+    }
+    // Check if this name looks like IPv6
+    if (target.contains(":")) {
+      return false;
+    }
+    // Check if this might be IPv4.  Such addresses have no alphabetic characters.  This also
+    // checks the target is empty.
+    boolean alldigits = true;
+    for (int i = 0; i < target.length(); i++) {
+      char c = target.charAt(i);
+      if (c != '.') {
+        alldigits &= (c >= '0' && c <= '9');
+      }
+    }
+    return !alldigits;
+  }
 }
diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
index d0db539..cddbe3f 100644
--- a/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
@@ -17,7 +17,6 @@
 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;
@@ -53,8 +52,7 @@
           name,
           params,
           GrpcUtil.SHARED_CHANNEL_EXECUTOR,
-          GrpcUtil.getDefaultProxyDetector(),
-          Stopwatch.createUnstarted());
+          GrpcUtil.getDefaultProxyDetector());
     } else {
       return null;
     }
diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
index 67da06f..6a63864 100644
--- a/core/src/main/java/io/grpc/internal/GrpcAttributes.java
+++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
@@ -17,6 +17,10 @@
 package io.grpc.internal;
 
 import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.Grpc;
+import io.grpc.NameResolver;
+import io.grpc.SecurityLevel;
 import java.util.Map;
 
 /**
@@ -26,6 +30,7 @@
   /**
    * Attribute key for service config.
    */
+  @NameResolver.ResolutionResultAttr
   public static final Attributes.Key<Map<String, Object>> NAME_RESOLVER_SERVICE_CONFIG =
       Attributes.Key.create("service-config");
 
@@ -33,6 +38,7 @@
    * The naming authority of a gRPC LB server address.  It is an address-group-level attribute,
    * present when the address group is a LoadBalancer.
    */
+  @EquivalentAddressGroup.Attr
   public static final Attributes.Key<String> ATTR_LB_ADDR_AUTHORITY =
       Attributes.Key.create("io.grpc.grpclb.lbAddrAuthority");
 
@@ -40,8 +46,18 @@
    * 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.
    */
+  @EquivalentAddressGroup.Attr
   public static final Attributes.Key<Boolean> ATTR_LB_PROVIDED_BACKEND =
       Attributes.Key.create("io.grpc.grpclb.lbProvidedBackend");
 
+  /**
+   * The security level of the transport.  If it's not present, {@link SecurityLevel#NONE} should be
+   * assumed.
+   */
+  @SuppressWarnings("deprecation")
+  @Grpc.TransportAttr
+  public static final Attributes.Key<SecurityLevel> ATTR_SECURITY_LEVEL =
+      io.grpc.CallCredentials.ATTR_SECURITY_LEVEL;
+
   private GrpcAttributes() {}
 }
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
index 5398299..80fd2f5 100644
--- a/core/src/main/java/io/grpc/internal/GrpcUtil.java
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -200,7 +200,7 @@
 
   public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
 
-  private static final String IMPLEMENTATION_VERSION = "1.16.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
+  private static final String IMPLEMENTATION_VERSION = "1.16.1"; // CURRENT_GRPC_VERSION
 
   /**
    * The default delay in nanos before we send a keepalive.
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
index dc203b0..e1f840e 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -125,13 +125,7 @@
   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 final ChannelExecutor channelExecutor = new PanicChannelExecutor();
 
   private boolean fullStreamDecompression;
 
@@ -236,34 +230,7 @@
 
   // 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();
-        }
-      };
+      new DelayedTransportListener();
 
   // Must be called from channelExecutor
   private void maybeShutdownNowSubchannels() {
@@ -279,27 +246,12 @@
 
   // 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();
-        }
-      };
+  final InUseStateAggregator<Object> inUseStateAggregator = new IdleModeStateAggregator();
 
   @Override
   public ListenableFuture<ChannelStats> getStats() {
     final SettableFuture<ChannelStats> ret = SettableFuture.create();
-    // subchannels and oobchannels can only be accessed from channelExecutor
-    channelExecutor.executeLater(new Runnable() {
+    final class StatsFetcher implements Runnable {
       @Override
       public void run() {
         ChannelStats.Builder builder = new InternalChannelz.ChannelStats.Builder();
@@ -314,7 +266,10 @@
         builder.setSubchannels(children);
         ret.set(builder.build());
       }
-    }).drain();
+    }
+
+    // subchannels and oobchannels can only be accessed from channelExecutor
+    channelExecutor.executeLater(new StatsFetcher()).drain();
     return ret;
   }
 
@@ -463,7 +418,7 @@
     }
   }
 
-  private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
+  private final class ChannelTransportProvider implements ClientTransportProvider {
     @Override
     public ClientTransport get(PickSubchannelArgs args) {
       SubchannelPicker pickerCopy = subchannelPicker;
@@ -473,12 +428,14 @@
         return delayedTransport;
       }
       if (pickerCopy == null) {
-        channelExecutor.executeLater(new Runnable() {
-            @Override
-            public void run() {
-              exitIdleMode();
-            }
-          }).drain();
+        final class ExitIdleModeForTransport implements Runnable {
+          @Override
+          public void run() {
+            exitIdleMode();
+          }
+        }
+
+        channelExecutor.executeLater(new ExitIdleModeForTransport()).drain();
         return delayedTransport;
       }
       // There is no need to reschedule the idle timer here.
@@ -507,11 +464,21 @@
         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) {
+      final class RetryStream extends RetriableStream<ReqT> {
+        RetryStream() {
+          super(
+              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);
@@ -534,9 +501,13 @@
             context.detach(origContext);
           }
         }
-      };
+      }
+
+      return new RetryStream();
     }
-  };
+  }
+
+  private final ClientTransportProvider transportProvider = new ChannelTransportProvider();
 
   private final Rescheduler idleTimer;
 
@@ -552,8 +523,16 @@
     this.nameResolverFactory = builder.getNameResolverFactory();
     this.nameResolverParams = checkNotNull(builder.getNameResolverParams(), "nameResolverParams");
     this.nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
+    this.timeProvider = checkNotNull(timeProvider, "timeProvider");
+    maxTraceEvents = builder.maxTraceEvents;
+    if (maxTraceEvents > 0) {
+      long currentTimeNanos = timeProvider.currentTimeNanos();
+      channelTracer = new ChannelTracer(builder.maxTraceEvents, currentTimeNanos, "Channel");
+    } else {
+      channelTracer = null;
+    }
     if (builder.loadBalancerFactory == null) {
-      this.loadBalancerFactory = new AutoConfiguredLoadBalancerFactory();
+      this.loadBalancerFactory = new AutoConfiguredLoadBalancerFactory(channelTracer, timeProvider);
     } else {
       this.loadBalancerFactory = builder.loadBalancerFactory;
     }
@@ -568,7 +547,7 @@
     this.retryEnabled = builder.retryEnabled && !builder.temporarilyDisableRetry;
     serviceConfigInterceptor = new ServiceConfigInterceptor(
         retryEnabled, builder.maxRetryAttempts, builder.maxHedgedAttempts);
-    Channel channel = new RealChannel();
+    Channel channel = new RealChannel(nameResolver.getServiceAuthority());
     channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor);
     if (builder.binlog != null) {
       channel = builder.binlog.wrapChannel(channel);
@@ -606,24 +585,18 @@
 
     this.channelBufferLimit = builder.retryBufferSize;
     this.perRpcBufferLimit = builder.perRpcBufferLimit;
-    this.timeProvider = checkNotNull(timeProvider, "timeProvider");
-    this.callTracerFactory = new CallTracer.Factory() {
+    final class ChannelCallTracerFactory implements CallTracer.Factory {
       @Override
       public CallTracer create() {
         return new CallTracer(timeProvider);
       }
-    };
+    }
+
+    this.callTracerFactory = new ChannelCallTracerFactory();
     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});
   }
 
@@ -687,7 +660,7 @@
     // 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() {
+    final class Shutdown implements Runnable {
       @Override
       public void run() {
         if (channelTracer != null) {
@@ -699,15 +672,19 @@
         }
         channelStateManager.gotoState(SHUTDOWN);
       }
-    });
+    }
+
+    channelExecutor.executeLater(new Shutdown());
 
     uncommittedRetriableStreamsRegistry.onShutdown(SHUTDOWN_STATUS);
-    channelExecutor.executeLater(new Runnable() {
-        @Override
-        public void run() {
-          cancelIdleTimer(/* permanent= */ true);
-        }
-      }).drain();
+    final class CancelIdleTimer implements Runnable {
+      @Override
+      public void run() {
+        cancelIdleTimer(/* permanent= */ true);
+      }
+    }
+
+    channelExecutor.executeLater(new CancelIdleTimer()).drain();
     logger.log(Level.FINE, "[{0}] Shutting down", getLogId());
     return this;
   }
@@ -722,16 +699,18 @@
     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();
+    final class ShutdownNow implements Runnable {
+      @Override
+      public void run() {
+        if (shutdownNowed) {
+          return;
         }
-      }).drain();
+        shutdownNowed = true;
+        maybeShutdownNowSubchannels();
+      }
+    }
+
+    channelExecutor.executeLater(new ShutdownNow()).drain();
     return this;
   }
 
@@ -745,16 +724,18 @@
     panicMode = true;
     cancelIdleTimer(/* permanent= */ true);
     shutdownNameResolverAndLoadBalancer(false);
-    SubchannelPicker newPicker = new SubchannelPicker() {
-      final PickResult panicPickResult =
+    final class PanicSubchannelPicker extends SubchannelPicker {
+      private 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);
+    }
+
+    updateSubchannelPicker(new PanicSubchannelPicker());
     if (channelTracer != null) {
       channelTracer.reportEvent(
           new ChannelTrace.Event.Builder()
@@ -810,6 +791,14 @@
   }
 
   private class RealChannel extends Channel {
+    // Set when the NameResolver is initially created. When we create a new NameResolver for the
+    // same target, the new instance must have the same value.
+    private final String authority;
+
+    private RealChannel(String authority) {
+      this.authority =  checkNotNull(authority, "authority");
+    }
+
     @Override
     public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(MethodDescriptor<ReqT, RespT> method,
         CallOptions callOptions) {
@@ -828,8 +817,7 @@
 
     @Override
     public String authority() {
-      String authority = nameResolver.getServiceAuthority();
-      return checkNotNull(authority, "authority");
+      return authority;
     }
   }
 
@@ -856,60 +844,61 @@
   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();
+      final class RequestConnection implements Runnable {
+        @Override
+        public void run() {
+          exitIdleMode();
+          if (subchannelPicker != null) {
+            subchannelPicker.requestConnection();
+          }
+        }
+      }
+
+      channelExecutor.executeLater(new 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();
+    final class NotifyStateChanged implements Runnable {
+      @Override
+      public void run() {
+        channelStateManager.notifyWhenStateChanged(callback, executor, source);
+      }
+    }
+
+    channelExecutor.executeLater(new NotifyStateChanged()).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();
+    final class ResetConnectBackoff implements 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();
+        }
+      }
+    }
+
+    channelExecutor.executeLater(new ResetConnectBackoff()).drain();
   }
 
   @Override
   public void enterIdle() {
-    class PrepareToLoseNetworkRunnable implements Runnable {
+    final class PrepareToLoseNetworkRunnable implements Runnable {
       @Override
       public void run() {
         if (shutdown.get() || lbHelper == null) {
@@ -933,7 +922,7 @@
     final Object lock = new Object();
 
     @GuardedBy("lock")
-    Collection<ClientStream> uncommittedRetriableStreams = new HashSet<ClientStream>();
+    Collection<ClientStream> uncommittedRetriableStreams = new HashSet<>();
 
     @GuardedBy("lock")
     Status shutdownStatus;
@@ -996,7 +985,7 @@
           shutdownStatusCopy = shutdownStatus;
           // Because retriable transport is long-lived, we take this opportunity to down-size the
           // hashmap.
-          uncommittedRetriableStreams = new HashSet<ClientStream>();
+          uncommittedRetriableStreams = new HashSet<>();
         }
       }
 
@@ -1034,6 +1023,36 @@
       if (maxTraceEvents > 0) {
         subchannelTracer = new ChannelTracer(maxTraceEvents, subchannelCreationTime, "Subchannel");
       }
+
+      final class ManagedInternalSubchannelCallback extends 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);
+        }
+      }
+
       final InternalSubchannel internalSubchannel = new InternalSubchannel(
           addressGroups,
           authority(),
@@ -1043,34 +1062,7 @@
           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);
-            }
-          },
+          new ManagedInternalSubchannelCallback(),
           channelz,
           callTracerFactory.create(),
           subchannelTracer,
@@ -1087,24 +1079,27 @@
       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);
-            }
+
+      final class AddSubchannel implements 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);
+          }
+        }
+      }
+
+      runSerialized(new AddSubchannel());
       return subchannel;
     }
 
@@ -1113,30 +1108,30 @@
         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);
-              }
+      final class UpdateBalancingState implements 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);
+          }
+        }
+      }
+
+      runSerialized(new UpdateBalancingState());
     }
 
     @Override
@@ -1169,26 +1164,28 @@
             .build());
         subchannelTracer = new ChannelTracer(maxTraceEvents, oobChannelCreationTime, "Subchannel");
       }
+      final class ManagedOobChannelCallback extends 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);
+        }
+      }
+
       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);
-            }
-          },
+          new ManagedOobChannelCallback(),
           channelz,
           callTracerFactory.create(),
           subchannelTracer,
@@ -1204,19 +1201,21 @@
       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);
-            }
+      final class AddOobChannel implements 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);
+          }
+        }
+      }
+
+      runSerialized(new AddOobChannel());
       return oobChannel;
     }
 
@@ -1325,41 +1324,40 @@
             .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();
+      final class NameResolverErrorHandler 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;
+          }
+          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);
+        }
+      }
+
+      channelExecutor.executeLater(new NameResolverErrorHandler()).drain();
     }
   }
 
@@ -1419,14 +1417,16 @@
         // TODO(zhangkun83): consider a better approach
         // (https://github.com/grpc/grpc-java/issues/2562).
         if (!terminating) {
+          final class ShutdownSubchannel implements Runnable {
+            @Override
+            public void run() {
+              subchannel.shutdown(SUBCHANNEL_SHUTDOWN_STATUS);
+            }
+          }
+
           delayedShutdownTask = transportFactory.getScheduledExecutorService().schedule(
               new LogExceptionRunnable(
-                  new Runnable() {
-                    @Override
-                    public void run() {
-                      subchannel.shutdown(SUBCHANNEL_SHUTDOWN_STATUS);
-                    }
-                  }), SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
+                  new ShutdownSubchannel()), SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
           return;
         }
       }
@@ -1463,4 +1463,62 @@
         .add("target", target)
         .toString();
   }
+
+  private final class PanicChannelExecutor extends ChannelExecutor {
+    @Override
+    void handleUncaughtThrowable(Throwable t) {
+      super.handleUncaughtThrowable(t);
+      panic(t);
+    }
+  }
+
+  /**
+   * Called from channelExecutor.
+   */
+  private final class DelayedTransportListener implements 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 accessed from channelExecutor.
+   */
+  private final class IdleModeStateAggregator extends InUseStateAggregator<Object> {
+    @Override
+    void handleInUse() {
+      exitIdleMode();
+    }
+
+    @Override
+    void handleNotInUse() {
+      if (shutdown.get()) {
+        return;
+      }
+      rescheduleIdleTimer();
+    }
+  }
 }
diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
index 48e0066..56548b2 100644
--- a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
+++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
@@ -20,7 +20,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
-import io.grpc.CallCredentials.MetadataApplier;
+import io.grpc.CallCredentials2.MetadataApplier;
 import io.grpc.CallOptions;
 import io.grpc.Context;
 import io.grpc.Metadata;
@@ -29,7 +29,7 @@
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.GuardedBy;
 
-final class MetadataApplierImpl implements MetadataApplier {
+final class MetadataApplierImpl extends MetadataApplier {
   private final ClientTransport transport;
   private final MethodDescriptor<?, ?> method;
   private final Metadata origHeaders;
diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
new file mode 100644
index 0000000..e2a9720
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.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.util;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.ExperimentalApi;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.LoadBalancer;
+import io.grpc.ManagedChannel;
+import io.grpc.NameResolver;
+import java.util.List;
+
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+public abstract class ForwardingLoadBalancerHelper extends LoadBalancer.Helper {
+  /**
+   * Returns the underlying helper.
+   */
+  protected abstract LoadBalancer.Helper delegate();
+
+  @Override
+  public Subchannel createSubchannel(EquivalentAddressGroup addrs, Attributes attrs) {
+    return delegate().createSubchannel(addrs, attrs);
+  }
+
+  @Override
+  public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+    return delegate().createSubchannel(addrs, attrs);
+  }
+
+  @Override
+  public void updateSubchannelAddresses(
+      Subchannel subchannel, EquivalentAddressGroup addrs) {
+    delegate().updateSubchannelAddresses(subchannel, addrs);
+  }
+
+  @Override
+  public void updateSubchannelAddresses(
+      Subchannel subchannel, List<EquivalentAddressGroup> addrs) {
+    delegate().updateSubchannelAddresses(subchannel, addrs);
+  }
+
+  @Override
+  public  ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) {
+    return delegate().createOobChannel(eag, authority);
+  }
+
+  @Override
+  public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) {
+    delegate().updateOobChannelAddresses(channel, eag);
+  }
+
+  @Override
+  public void updateBalancingState(
+      ConnectivityState newState, SubchannelPicker newPicker) {
+    delegate().updateBalancingState(newState, newPicker);
+  }
+
+  @Override
+  public void runSerialized(Runnable task) {
+    delegate().runSerialized(task);
+  }
+
+  @Override
+  public NameResolver.Factory getNameResolverFactory() {
+    return delegate().getNameResolverFactory();
+  }
+
+  @Override
+  public String getAuthority() {
+    return delegate().getAuthority();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/test/java/io/grpc/ForwardingTestUtil.java b/core/src/test/java/io/grpc/ForwardingTestUtil.java
index ce9ca5e..e9f1c5b 100644
--- a/core/src/test/java/io/grpc/ForwardingTestUtil.java
+++ b/core/src/test/java/io/grpc/ForwardingTestUtil.java
@@ -28,6 +28,7 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Collection;
+import javax.annotation.Nullable;
 
 /**
  * A util class to help test forwarding classes.
@@ -35,9 +36,8 @@
 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.
+   * methods to the delegate, except for those in skippedMethods.  This does NOT verify that
+   * arguments or return values are forwarded properly.
    *
    * @param delegateClass The class whose methods should be forwarded.
    * @param mockDelegate The mockito mock of the delegate class.
@@ -49,6 +49,34 @@
       T mockDelegate,
       T forwarder,
       Collection<Method> skippedMethods) throws Exception {
+    testMethodsForwarded(
+        delegateClass, mockDelegate, forwarder, skippedMethods,
+        new ArgumentProvider() {
+          @Override
+          public Object get(Class<?> clazz) {
+            return null;
+          }
+        });
+  }
+
+  /**
+   * 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 return
+   * values are forwarded properly, and can only verify the propagation of arguments for which
+   * {@code argProvider} returns distinctive non-null values.
+   *
+   * @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.
+   * @param argProvider provides argument to be passed to tested forwarding methods.
+   */
+  public static <T> void testMethodsForwarded(
+      Class<T> delegateClass,
+      T mockDelegate,
+      T forwarder,
+      Collection<Method> skippedMethods,
+      ArgumentProvider argProvider) throws Exception {
     assertTrue(mockingDetails(mockDelegate).isMock());
     assertFalse(mockingDetails(forwarder).isMock());
 
@@ -61,7 +89,9 @@
       Class<?>[] argTypes = method.getParameterTypes();
       Object[] args = new Object[argTypes.length];
       for (int i = 0; i < argTypes.length; i++) {
-        args[i] = Defaults.defaultValue(argTypes[i]);
+        if ((args[i] = argProvider.get(argTypes[i])) == null) {
+          args[i] = Defaults.defaultValue(argTypes[i]);
+        }
       }
       method.invoke(forwarder, args);
       try {
@@ -85,4 +115,20 @@
       assertEquals("Method toString() was not forwarded properly", expected, actual);
     }
   }
+
+  /**
+   * Provides arguments for forwarded methods tested in {@link #testMethodsForwarded}.
+   */
+  public interface ArgumentProvider {
+    /**
+     * Return an instance of the given class to be used as an argument passed to one method call.
+     * If one method has multiple arguments with the same type, each occurrence will call this
+     * method once.  It is recommended that each invocation returns a distinctive object for the
+     * same type, in order to verify that arguments are passed by the tested class correctly.
+     *
+     * @return a value to be passed as an argument.  If {@code null}, {@link Default#defaultValue}
+     *         will be used.
+     */
+    @Nullable Object get(Class<?> clazz);
+  }
 }
diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
index 1542a6f..71c5742 100644
--- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
+++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
@@ -19,11 +19,14 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
 
 import io.grpc.Attributes;
 import io.grpc.ConnectivityState;
 import io.grpc.ConnectivityStateInfo;
 import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
 import io.grpc.LoadBalancer;
 import io.grpc.LoadBalancer.Helper;
 import io.grpc.LoadBalancer.Subchannel;
@@ -52,7 +55,8 @@
  */
 @RunWith(JUnit4.class)
 public class AutoConfiguredLoadBalancerFactoryTest {
-  private final AutoConfiguredLoadBalancerFactory lbf = new AutoConfiguredLoadBalancerFactory();
+  private final AutoConfiguredLoadBalancerFactory lbf =
+      new AutoConfiguredLoadBalancerFactory(null, null);
 
   @Test
   public void newLoadBalancer_isAuto() {
@@ -258,6 +262,80 @@
     }
   }
 
+  @Test
+  public void channelTracing_lbPolicyChanged() {
+    ChannelTracer channelTracer = new ChannelTracer(100, 1000, "dummy_type");
+    TimeProvider timeProvider = new TimeProvider() {
+      @Override
+      public long currentTimeNanos() {
+        return 101;
+      }
+    };
+
+    InternalChannelz.ChannelStats.Builder statsBuilder
+        = new InternalChannelz.ChannelStats.Builder();
+    channelTracer.updateBuilder(statsBuilder);
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(new SocketAddress(){}, Attributes.EMPTY));
+    Helper helper = new TestHelper() {
+      @Override
+      public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+        return new TestSubchannel(addrs, attrs);
+      }
+
+      @Override
+      public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) {
+        return mock(ManagedChannel.class, RETURNS_DEEP_STUBS);
+      }
+
+      @Override
+      public String getAuthority() {
+        return "fake_authority";
+      }
+
+      @Override
+      public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
+        // noop
+      }
+    };
+    int prevNumOfEvents = statsBuilder.build().channelTrace.events.size();
+
+    LoadBalancer lb =
+        new AutoConfiguredLoadBalancerFactory(channelTracer, timeProvider).newLoadBalancer(helper);
+    lb.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+    channelTracer.updateBuilder(statsBuilder);
+    assertThat(statsBuilder.build().channelTrace.events).hasSize(prevNumOfEvents);
+
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("loadBalancingPolicy", "round_robin");
+    lb.handleResolvedAddressGroups(servers,
+        Attributes.newBuilder()
+            .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build());
+    channelTracer.updateBuilder(statsBuilder);
+    assertThat(statsBuilder.build().channelTrace.events).hasSize(prevNumOfEvents + 1);
+    assertThat(statsBuilder.build().channelTrace.events.get(prevNumOfEvents).description)
+        .isEqualTo("Load balancer changed from PickFirstBalancer to RoundRobinLoadBalancer");
+    prevNumOfEvents = statsBuilder.build().channelTrace.events.size();
+
+    serviceConfig.put("loadBalancingPolicy", "round_robin");
+    lb.handleResolvedAddressGroups(servers,
+        Attributes.newBuilder()
+            .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build());
+    channelTracer.updateBuilder(statsBuilder);
+    assertThat(statsBuilder.build().channelTrace.events).hasSize(prevNumOfEvents);
+
+    servers = Collections.singletonList(new EquivalentAddressGroup(
+        new SocketAddress(){},
+        Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()));
+    lb.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+
+    channelTracer.updateBuilder(statsBuilder);
+    assertThat(statsBuilder.build().channelTrace.events).hasSize(prevNumOfEvents + 1);
+    assertThat(statsBuilder.build().channelTrace.events.get(prevNumOfEvents).description)
+        .isEqualTo("Load balancer changed from RoundRobinLoadBalancer to GrpclbLoadBalancer");
+  }
+
   public static class ForwardingLoadBalancer extends LoadBalancer {
     private final LoadBalancer delegate;
 
diff --git a/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java
new file mode 100644
index 0000000..fd2b2c1
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.RequestInfo;
+import io.grpc.CallCredentials2;
+import io.grpc.CallCredentials2.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 CallCredentials2} applying functionality implemented by {@link
+ * CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}.
+ */
+@RunWith(JUnit4.class)
+public class CallCredentials2ApplyingTest {
+  @Mock
+  private ClientTransportFactory mockTransportFactory;
+
+  @Mock
+  private ConnectionClientTransport mockTransport;
+
+  @Mock
+  private ClientStream mockStream;
+
+  @Mock
+  private CallCredentials2 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<RequestInfo> infoCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(
+        infoCaptor.capture(), same(mockExecutor), any(MetadataApplier.class));
+    RequestInfo info = infoCaptor.getValue();
+    assertSame(method, info.getMethodDescriptor());
+    Attributes attrs = info.getTransportAttrs();
+    assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY));
+    assertSame(AUTHORITY, info.getAuthority());
+    assertSame(SecurityLevel.NONE, info.getSecurityLevel());
+  }
+
+  @Test
+  public void parameterPropagation_transportSetSecurityLevel() {
+    Attributes transportAttrs = Attributes.newBuilder()
+        .set(ATTR_KEY, ATTR_VALUE)
+        .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
+        .build();
+    when(mockTransport.getAttributes()).thenReturn(transportAttrs);
+
+    transport.newStream(method, origHeaders, callOptions);
+
+    ArgumentCaptor<RequestInfo> infoCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(
+        infoCaptor.capture(), same(mockExecutor), any(MetadataApplier.class));
+    RequestInfo info = infoCaptor.getValue();
+    assertSame(method, info.getMethodDescriptor());
+    assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY));
+    assertSame(AUTHORITY, info.getAuthority());
+    assertSame(SecurityLevel.INTEGRITY, info.getSecurityLevel());
+  }
+
+  @Test
+  public void parameterPropagation_callOptionsSetAuthority() {
+    Attributes transportAttrs = Attributes.newBuilder()
+        .set(ATTR_KEY, ATTR_VALUE)
+        .build();
+    when(mockTransport.getAttributes()).thenReturn(transportAttrs);
+    Executor anotherExecutor = mock(Executor.class);
+
+    transport.newStream(method, origHeaders,
+        callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor));
+
+    ArgumentCaptor<RequestInfo> infoCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(
+        infoCaptor.capture(), same(anotherExecutor), any(MetadataApplier.class));
+    RequestInfo info = infoCaptor.getValue();
+    assertSame(method, info.getMethodDescriptor());
+    assertSame(ATTR_VALUE, info.getTransportAttrs().get(ATTR_KEY));
+    assertEquals("calloptions-authority", info.getAuthority());
+    assertSame(SecurityLevel.NONE, info.getSecurityLevel());
+  }
+
+  @Test
+  public void credentialThrows() {
+    final RuntimeException ex = new RuntimeException();
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+    doThrow(ex).when(mockCreds).applyRequestMetadata(
+        any(RequestInfo.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()[2];
+          Metadata headers = new Metadata();
+          headers.put(CREDS_KEY, CREDS_VALUE);
+          applier.apply(headers);
+          return null;
+        }
+      }).when(mockCreds).applyRequestMetadata(
+          any(RequestInfo.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()[2];
+          applier.fail(error);
+          return null;
+        }
+      }).when(mockCreds).applyRequestMetadata(
+          any(RequestInfo.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(
+        any(RequestInfo.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(
+        any(RequestInfo.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/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
index c6a9bfb..a8ca121 100644
--- a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
+++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
@@ -30,7 +30,6 @@
 
 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;
@@ -55,6 +54,7 @@
  * CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}.
  */
 @RunWith(JUnit4.class)
+@Deprecated
 public class CallCredentialsApplyingTest {
   @Mock
   private ClientTransportFactory mockTransportFactory;
@@ -127,7 +127,7 @@
 
     ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
     verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
-        any(MetadataApplier.class));
+        any(CallCredentials.MetadataApplier.class));
     Attributes attrs = attrsCaptor.getValue();
     assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
     assertSame(AUTHORITY, attrs.get(CallCredentials.ATTR_AUTHORITY));
@@ -147,7 +147,7 @@
 
     ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
     verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
-        any(MetadataApplier.class));
+        any(CallCredentials.MetadataApplier.class));
     Attributes attrs = attrsCaptor.getValue();
     assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
     assertEquals("transport-override-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
@@ -169,7 +169,7 @@
 
     ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
     verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(),
-        same(anotherExecutor), any(MetadataApplier.class));
+        same(anotherExecutor), any(CallCredentials.MetadataApplier.class));
     Attributes attrs = attrsCaptor.getValue();
     assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
     assertEquals("calloptions-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
@@ -181,7 +181,8 @@
     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));
+        same(method), any(Attributes.class), same(mockExecutor),
+        any(CallCredentials.MetadataApplier.class));
 
     FailingClientStream stream =
         (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
@@ -197,14 +198,15 @@
     doAnswer(new Answer<Void>() {
         @Override
         public Void answer(InvocationOnMock invocation) throws Throwable {
-          MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
+          CallCredentials.MetadataApplier applier =
+              (CallCredentials.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));
+          same(mockExecutor), any(CallCredentials.MetadataApplier.class));
 
     ClientStream stream = transport.newStream(method, origHeaders, callOptions);
 
@@ -221,12 +223,13 @@
     doAnswer(new Answer<Void>() {
         @Override
         public Void answer(InvocationOnMock invocation) throws Throwable {
-          MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
+          CallCredentials.MetadataApplier applier =
+              (CallCredentials.MetadataApplier) invocation.getArguments()[3];
           applier.fail(error);
           return null;
         }
       }).when(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
-          same(mockExecutor), any(MetadataApplier.class));
+          same(mockExecutor), any(CallCredentials.MetadataApplier.class));
 
     FailingClientStream stream =
         (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
@@ -242,7 +245,7 @@
     // Will call applyRequestMetadata(), which is no-op.
     DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
 
-    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
+    ArgumentCaptor<CallCredentials.MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
     verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
         same(mockExecutor), applierCaptor.capture());
     verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
@@ -264,7 +267,7 @@
     // Will call applyRequestMetadata(), which is no-op.
     DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
 
-    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
+    ArgumentCaptor<CallCredentials.MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
     verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
         same(mockExecutor), applierCaptor.capture());
 
diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
index 44bde22..3f7adfa 100644
--- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
+++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
@@ -18,6 +18,7 @@
 
 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.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -27,13 +28,10 @@
 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;
@@ -42,6 +40,7 @@
 import io.grpc.internal.DnsNameResolver.ResourceResolver;
 import io.grpc.internal.DnsNameResolver.ResourceResolverFactory;
 import io.grpc.internal.SharedResourceHolder.Resource;
+import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -55,8 +54,6 @@
 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;
@@ -108,25 +105,21 @@
   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());
+    return newResolver(name, port, GrpcUtil.NOOP_PROXY_DETECTOR);
   }
 
   private DnsNameResolver newResolver(
       String name,
       int port,
-      ProxyDetector proxyDetector,
-      Stopwatch stopwatch) {
+      ProxyDetector proxyDetector) {
     DnsNameResolver dnsResolver = new DnsNameResolver(
         null,
         name,
         Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, port).build(),
         fakeExecutorResource,
-        proxyDetector,
-        stopwatch);
+        proxyDetector);
     return dnsResolver;
   }
 
@@ -134,19 +127,6 @@
   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
@@ -198,8 +178,7 @@
   }
 
   @Test
-  public void resolve_neverCache() throws Exception {
-    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "0");
+  public void resolve() throws Exception {
     final List<InetAddress> answer1 = createAddressList(2);
     final List<InetAddress> answer2 = createAddressList(1);
     String name = "foo.googleapis.com";
@@ -227,156 +206,6 @@
   }
 
   @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);
@@ -397,6 +226,23 @@
   }
 
   @Test
+  public void resolveAll_nullResourceResolver_addressFailure() throws Exception {
+    final String hostname = "addr.fake";
+
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString()))
+        .thenThrow(new IOException("no addr"));
+    ResourceResolver resourceResolver = null;
+    boolean resovleSrv = true;
+    boolean resolveTxt = true;
+
+    thrown.expect(RuntimeException.class);
+    thrown.expectMessage("no addr");
+
+    DnsNameResolver.resolveAll(mockResolver, resourceResolver, resovleSrv, resolveTxt, hostname);
+  }
+
+  @Test
   public void resolveAll_presentResourceResolver() throws Exception {
     final String hostname = "addr.fake";
     final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
@@ -502,8 +348,7 @@
         "password");
     when(alwaysDetectProxy.proxyFor(any(SocketAddress.class)))
         .thenReturn(proxyParameters);
-    DnsNameResolver resolver =
-        newResolver(name, port, alwaysDetectProxy, Stopwatch.createUnstarted());
+    DnsNameResolver resolver = newResolver(name, port, alwaysDetectProxy);
     AddressResolver mockAddressResolver = mock(AddressResolver.class);
     when(mockAddressResolver.resolveAddress(Matchers.anyString())).thenThrow(new AssertionError());
     resolver.setAddressResolver(mockAddressResolver);
@@ -754,6 +599,82 @@
     assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
   }
 
+  @Test
+  public void shouldUseJndi_alwaysFalseIfDisabled() {
+    boolean enableJndi = false;
+    boolean enableJndiLocalhost = true;
+    String host = "seemingly.valid.host";
+
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, host));
+  }
+
+  @Test
+  public void shouldUseJndi_falseIfDisabledForLocalhost() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = false;
+    
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "localhost"));
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "LOCALHOST"));
+  }
+
+  @Test
+  public void shouldUseJndi_trueIfLocalhostOverriden() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = true;
+    String host = "localhost";
+
+    assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, host));
+  }
+
+  @Test
+  public void shouldUseJndi_falseForIpv6() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = false;
+
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "::"));
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "::1"));
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "2001:db8:1234::"));
+    assertFalse(DnsNameResolver.shouldUseJndi(
+        enableJndi, enableJndiLocalhost, "[2001:db8:1234::]"));
+    assertFalse(DnsNameResolver.shouldUseJndi(
+        enableJndi, enableJndiLocalhost, "2001:db8:1234::%3"));
+  }
+
+  @Test
+  public void shouldUseJndi_falseForIpv4() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = false;
+
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "127.0.0.1"));
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "192.168.0.1"));
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "134744072"));
+  }
+
+  @Test
+  public void shouldUseJndi_falseForEmpty() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = false;
+
+    assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, ""));
+  }
+
+  @Test
+  public void shouldUseJndi_trueIfItMightPossiblyBeValid() {
+    boolean enableJndi = true;
+    boolean enableJndiLocalhost = false;
+
+    assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "remotehost"));
+    assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "remotehost.gov"));
+    assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "f.q.d.n."));
+    assertTrue(DnsNameResolver.shouldUseJndi(
+        enableJndi, enableJndiLocalhost, "8.8.8.8.in-addr.arpa."));
+    assertTrue(DnsNameResolver.shouldUseJndi(
+        enableJndi, enableJndiLocalhost, "2001-db8-1234--as3.ipv6-literal.net"));
+
+
+
+  }
+
   private void testInvalidUri(URI uri) {
     try {
       provider.newNameResolver(uri, NAME_RESOLVER_PARAMS);
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
index ffc92b1..c5fdf71 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
@@ -57,7 +57,6 @@
 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;
@@ -1388,6 +1387,7 @@
    * propagated to newStream() and applyRequestMetadata().
    */
   @Test
+  @SuppressWarnings("deprecation")
   public void informationPropagatedToNewStreamAndCallCredentials() {
     createChannel();
     CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds);
@@ -1401,9 +1401,9 @@
           credsApplyContexts.add(Context.current());
           return null;
         }
-      }).when(creds).applyRequestMetadata(
+      }).when(creds).applyRequestMetadata(  // TODO(zhangkun83): remove suppression of deprecations
           any(MethodDescriptor.class), any(Attributes.class), any(Executor.class),
-          any(MetadataApplier.class));
+          any(CallCredentials.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
@@ -1435,7 +1435,7 @@
 
     verify(creds, never()).applyRequestMetadata(
         any(MethodDescriptor.class), any(Attributes.class), any(Executor.class),
-        any(MetadataApplier.class));
+        any(CallCredentials.MetadataApplier.class));
 
     // applyRequestMetadata() is called after the transport becomes ready.
     transportInfo.listener.transportReady();
@@ -1444,7 +1444,8 @@
     helper.updateBalancingState(READY, mockPicker);
     executor.runDueTasks();
     ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(Attributes.class);
-    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(MetadataApplier.class);
+    ArgumentCaptor<CallCredentials.MetadataApplier> applierCaptor
+        = ArgumentCaptor.forClass(CallCredentials.MetadataApplier.class);
     verify(creds).applyRequestMetadata(same(method), attrsCaptor.capture(),
         same(executor.getScheduledExecutorService()), applierCaptor.capture());
     assertEquals("testValue", testKey.get(credsApplyContexts.poll()));
@@ -2761,7 +2762,7 @@
 
     ManagedChannel mychannel = new CustomBuilder()
         .nameResolverFactory(factory)
-        .loadBalancerFactory(new AutoConfiguredLoadBalancerFactory()).build();
+        .loadBalancerFactory(new AutoConfiguredLoadBalancerFactory(null, null)).build();
 
     ClientCall<Void, Void> call1 =
         mychannel.newCall(TestMethodDescriptors.voidMethod(), CallOptions.DEFAULT);
@@ -2801,6 +2802,14 @@
     mychannel.shutdownNow();
   }
 
+  @Test
+  public void getAuthorityAfterShutdown() throws Exception {
+    createChannel();
+    assertEquals(SERVICE_NAME, channel.authority());
+    channel.shutdownNow().awaitTermination(1, TimeUnit.SECONDS);
+    assertEquals(SERVICE_NAME, channel.authority());
+  }
+
   private static final class ChannelBuilder
       extends AbstractManagedChannelImplBuilder<ChannelBuilder> {
 
diff --git a/core/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java b/core/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java
new file mode 100644
index 0000000..ebc883b
--- /dev/null
+++ b/core/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util;
+
+import static org.mockito.Mockito.mock;
+
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.ForwardingTestUtil;
+import io.grpc.LoadBalancer;
+import java.lang.reflect.Method;
+import java.net.SocketAddress;
+import java.util.Arrays;
+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 ForwardingLoadBalancerHelper}. */
+@RunWith(JUnit4.class)
+public class ForwardingLoadBalancerHelperTest {
+  private final LoadBalancer.Helper mockDelegate = mock(LoadBalancer.Helper.class);
+
+  private final class TestHelper extends ForwardingLoadBalancerHelper {
+    @Override
+    protected LoadBalancer.Helper delegate() {
+      return mockDelegate;
+    }
+  }
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    final SocketAddress mockAddr = mock(SocketAddress.class);
+    ForwardingTestUtil.testMethodsForwarded(
+        LoadBalancer.Helper.class,
+        mockDelegate,
+        new TestHelper(),
+        Collections.<Method>emptyList(),
+        new ForwardingTestUtil.ArgumentProvider() {
+          @Override
+          public Object get(Class<?> clazz) {
+            if (clazz.equals(EquivalentAddressGroup.class)) {
+              return new EquivalentAddressGroup(Arrays.asList(mockAddr));
+            } else if (clazz.equals(List.class)) {
+              return Collections.<Object>emptyList();
+            }
+            return null;
+          }
+        });
+  }
+}
diff --git a/cronet/build.gradle b/cronet/build.gradle
index ae0efd2..dd9209d 100644
--- a/cronet/build.gradle
+++ b/cronet/build.gradle
@@ -39,8 +39,8 @@
 }
 
 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 'io.grpc:grpc-core:1.16.1' // CURRENT_GRPC_VERSION
+    testImplementation 'io.grpc:grpc-testing:1.16.1' // CURRENT_GRPC_VERSION
 
     implementation "org.chromium.net:cronet-embedded:66.3359.158"
 
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
index 38b871d..102aad9 100644
--- a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
+++ b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
@@ -20,7 +20,6 @@
 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;
@@ -31,6 +30,7 @@
 import io.grpc.Status.Code;
 import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
 import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcAttributes;
 import io.grpc.internal.GrpcUtil;
 import io.grpc.internal.StatsTraceContext;
 import io.grpc.internal.TransportTracer;
@@ -95,8 +95,7 @@
     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)
+        .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
         .build();
   }
 
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
index 539ba1c..194e4c9 100644
--- a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
+++ b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
@@ -33,6 +33,7 @@
 import io.grpc.Status;
 import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
 import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.GrpcAttributes;
 import io.grpc.internal.ManagedClientTransport;
 import io.grpc.internal.TransportTracer;
 import io.grpc.testing.TestMethodDescriptors;
@@ -82,9 +83,8 @@
   @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));
+        SecurityLevel.PRIVACY_AND_INTEGRITY, attrs.get(GrpcAttributes.ATTR_SECURITY_LEVEL));
   }
 
   @Test
diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md
index cd316b4..4f82d2c 100644
--- a/documentation/android-channel-builder.md
+++ b/documentation/android-channel-builder.md
@@ -36,8 +36,8 @@
 `grpc-okhttp`:
 
 ```
-compile 'io.grpc:grpc-android:1.16.0' // CURRENT_GRPC_VERSION
-compile 'io.grpc:grpc-okhttp:1.16.0' // CURRENT_GRPC_VERSION
+compile 'io.grpc:grpc-android:1.16.1' // CURRENT_GRPC_VERSION
+compile 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
 ```
 
 You will also need permission to access the device's network state in your
diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle
index 1b69529..27e018d 100644
--- a/examples/android/clientcache/app/build.gradle
+++ b/examples/android/clientcache/app/build.gradle
@@ -31,7 +31,7 @@
     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
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' // CURRENT_GRPC_VERSION
         }
     }
     generateProtoTasks {
@@ -49,12 +49,12 @@
     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 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.1' // 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
+    testCompile 'io.grpc:grpc-testing:1.16.1' // CURRENT_GRPC_VERSION
 }
diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle
index a994ab4..1fdc4a5 100644
--- a/examples/android/helloworld/app/build.gradle
+++ b/examples/android/helloworld/app/build.gradle
@@ -30,7 +30,7 @@
     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
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' // CURRENT_GRPC_VERSION
         }
     }
     generateProtoTasks {
@@ -48,8 +48,8 @@
     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 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.1' // CURRENT_GRPC_VERSION
     compile 'javax.annotation:javax.annotation-api:1.2'
 }
diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle
index f92a996..75c8d4c 100644
--- a/examples/android/routeguide/app/build.gradle
+++ b/examples/android/routeguide/app/build.gradle
@@ -29,7 +29,7 @@
     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
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' // CURRENT_GRPC_VERSION
         }
     }
     generateProtoTasks {
@@ -47,8 +47,8 @@
     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 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.1' // CURRENT_GRPC_VERSION
     compile 'javax.annotation:javax.annotation-api:1.2'
 }
diff --git a/examples/build.gradle b/examples/build.gradle
index 1649e7f..aa46954 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -22,7 +22,7 @@
 
 // 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 grpcVersion = '1.16.1' // CURRENT_GRPC_VERSION
 def nettyTcNativeVersion = '2.0.7.Final'
 def protobufVersion = '3.5.1'
 def protocVersion = '3.5.1-1'
diff --git a/examples/example-kotlin/android/helloworld/app/build.gradle b/examples/example-kotlin/android/helloworld/app/build.gradle
index 64c8dc2..a7dcae3 100644
--- a/examples/example-kotlin/android/helloworld/app/build.gradle
+++ b/examples/example-kotlin/android/helloworld/app/build.gradle
@@ -52,7 +52,7 @@
     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
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' // CURRENT_GRPC_VERSION
         }
     }
     generateProtoTasks {
@@ -72,9 +72,9 @@
     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
+    compile 'io.grpc:grpc-okhttp:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.1' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.1' // CURRENT_GRPC_VERSION
 }
 
 repositories { mavenCentral() }
diff --git a/examples/example-kotlin/build.gradle b/examples/example-kotlin/build.gradle
index 4ebe65b..b8acca8 100644
--- a/examples/example-kotlin/build.gradle
+++ b/examples/example-kotlin/build.gradle
@@ -35,7 +35,7 @@
 
 // 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 grpcVersion = '1.16.1' // CURRENT_GRPC_VERSION
 
 dependencies {
     compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
diff --git a/examples/pom.xml b/examples/pom.xml
index 6de5524..31220ed 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -6,12 +6,12 @@
   <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 -->
+  <version>1.16.1</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 -->
+    <grpc.version>1.16.1</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>
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java
index 95ad813..c583e83 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java
@@ -215,9 +215,6 @@
     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) {
@@ -392,7 +389,6 @@
   @VisibleForTesting
   class FallbackModeTask implements Runnable {
     private ScheduledFuture<?> scheduledFuture;
-    private boolean discarded;
 
     @Override
     public void run() {
@@ -400,7 +396,6 @@
           @Override
           public void run() {
             checkState(fallbackTimer == FallbackModeTask.this, "fallback timer mismatch");
-            discarded = true;
             maybeUseFallbackBackends();
             maybeUpdatePicker();
           }
@@ -408,7 +403,6 @@
     }
 
     void cancel() {
-      discarded = true;
       scheduledFuture.cancel(false);
     }
 
diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
index e540528..9c39489 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
@@ -43,7 +43,6 @@
 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;
@@ -1077,29 +1076,6 @@
     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)
     //////////////////////////////////
@@ -1156,13 +1132,14 @@
     // Break the LB stream after the timer expires
     ////////////////////////////////////////////////
     if (timerExpires) {
+      Status streamError = Status.UNAVAILABLE.withDescription("OOB stream broken");
       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());
+      verify(mockLbService, times(2)).balanceLoad(lbResponseObserverCaptor.capture());
       lbResponseObserver = lbResponseObserverCaptor.getValue();
       assertEquals(1, lbRequestObservers.size());
       lbRequestObserver = lbRequestObservers.poll();
@@ -1198,6 +1175,60 @@
   }
 
   @Test
+  public void grpclbFallback_breakLbStreamBeforeFallbackTimerExpires() {
+    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));
+
+    /////////////////////////////////////////////
+    // Break the LB stream before timer expires
+    /////////////////////////////////////////////
+    Status streamError = Status.UNAVAILABLE.withDescription("OOB stream broken");
+    lbResponseObserver.onError(streamError.asException());
+
+    // Fall back to the backends from resolver
+    fallbackTestVerifyUseOfFallbackBackendLists(
+        inOrder, Arrays.asList(resolutionList.get(0), resolutionList.get(2)));
+
+    // 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()));
+  }
+
+  @Test
   public void grpclbFallback_balancerLost() {
     subtestGrpclbFallbackConnectionLost(true, false);
   }
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
index ac724f6..f4ffff4 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
@@ -1759,7 +1759,7 @@
     }
   }
 
-  /** Helper for getting remote address {@link io.grpc.ServerCall#getAttributes()} */
+  /** Helper for getting remote address from {@link io.grpc.ServerCall#getAttributes()} */
   protected SocketAddress obtainRemoteClientAddr() {
     TestServiceGrpc.TestServiceBlockingStub stub =
         blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS);
@@ -1769,6 +1769,16 @@
     return serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
   }
 
+  /** Helper for getting local address from {@link io.grpc.ServerCall#getAttributes()} */
+  protected SocketAddress obtainLocalClientAddr() {
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS);
+
+    stub.unaryCall(SimpleRequest.getDefaultInstance());
+
+    return serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
+  }
+
   /** Helper for asserting TLS info in SSLSession {@link io.grpc.ServerCall#getAttributes()} */
   protected void assertX500SubjectDn(String tlsInfo) {
     TestServiceGrpc.TestServiceBlockingStub stub =
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
index 57b65ea..5164fea 100644
--- a/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
@@ -88,6 +88,13 @@
   }
 
   @Test
+  public void localAddr() throws Exception {
+    InetSocketAddress isa = (InetSocketAddress) obtainLocalClientAddr();
+    assertEquals(InetAddress.getLoopbackAddress(), isa.getAddress());
+    assertEquals(getPort(), isa.getPort());
+  }
+
+  @Test
   public void tlsInfo() {
     assertX500SubjectDn("CN=testclient, O=Internet Widgits Pty Ltd, ST=Some-State, C=AU");
   }
diff --git a/netty/shaded/Android.bp b/netty/shaded/Android.bp
index 346371f..5aecf42 100644
--- a/netty/shaded/Android.bp
+++ b/netty/shaded/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2020 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.
@@ -13,10 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-// TODO: Build from source instead
-java_import_host {
+
+// The below module imports the Netty shaded binary from the Maven repository
+// since it's not trivial to build from source due to the large number of
+// external dependencies and missing build-system support.
+//
+// WARNING: The artifact version must match the source to avoid runtime issues.
+java_library_host {
     name: "grpc-java-netty-shaded",
-    jars: [
-        "grpc-netty-shaded-1.14.0.jar",
+    static_libs: [
+        "grpc-netty-shaded-1.16.1-jar",
     ],
 }
diff --git a/netty/shaded/grpc-netty-shaded-1.14.0.jar b/netty/shaded/grpc-netty-shaded-1.14.0.jar
deleted file mode 100644
index 230b7de..0000000
--- a/netty/shaded/grpc-netty-shaded-1.14.0.jar
+++ /dev/null
Binary files differ
diff --git a/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java
index 4711af9..bbceb36 100644
--- a/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java
+++ b/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java
@@ -18,8 +18,6 @@
 
 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
@@ -39,28 +37,17 @@
     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);
-  }
+  /** A class that provides a Netty handler to control protocol negotiation. */
+  public interface ProtocolNegotiatorFactory
+      extends NettyChannelBuilder.ProtocolNegotiatorFactory {}
 
   /**
-   * {@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}.
+   * Sets the {@link ProtocolNegotiatorFactory} to be used. Overrides any specified negotiation type
+   * and {@code SslContext}.
    */
-  public interface TransportCreationParamsFilter
-      extends NettyChannelBuilder.TransportCreationParamsFilter {}
-
-  public static void setDynamicTransportParamsFactory(
-      NettyChannelBuilder builder, TransportCreationParamsFilterFactory factory) {
-    builder.setDynamicParamsFactory(factory);
+  public static void setProtocolNegotiatorFactory(
+      NettyChannelBuilder builder, ProtocolNegotiatorFactory protocolNegotiator) {
+    builder.protocolNegotiatorFactory(protocolNegotiator);
   }
 
   public static void setStatsEnabled(NettyChannelBuilder builder, boolean value) {
diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
index 633cb32..46beec5 100644
--- a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
+++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
@@ -17,7 +17,6 @@
 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;
@@ -27,6 +26,7 @@
 import com.google.common.base.Preconditions;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
 import io.grpc.ExperimentalApi;
 import io.grpc.Internal;
 import io.grpc.NameResolver;
@@ -80,7 +80,8 @@
   private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
   private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
   private boolean keepAliveWithoutCalls;
-  private TransportCreationParamsFilterFactory dynamicParamsFactory;
+  private ProtocolNegotiatorFactory protocolNegotiatorFactory;
+  private LocalSocketPicker localSocketPicker;
 
   /**
    * Creates a new builder with the given server address. This factory method is primarily intended
@@ -327,13 +328,49 @@
     return this;
   }
 
+
+  /**
+   * If non-{@code null}, attempts to create connections bound to a local port.
+   */
+  public NettyChannelBuilder localSocketPicker(@Nullable LocalSocketPicker localSocketPicker) {
+    this.localSocketPicker = localSocketPicker;
+    return this;
+  }
+
+  /**
+   * This class is meant to be overriden with a custom implementation of
+   * {@link #createSocketAddress}.  The default implementation is a no-op.
+   *
+   * @since 1.16.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4917")
+  public static class LocalSocketPicker {
+
+    /**
+     * Called by gRPC to pick local socket to bind to.  This may be called multiple times.
+     * Subclasses are expected to override this method.
+     *
+     * @param remoteAddress the remote address to connect to.
+     * @param attrs the Attributes present on the {@link io.grpc.EquivalentAddressGroup} associated
+     *        with the address.
+     * @return a {@link SocketAddress} suitable for binding, or else {@code null}.
+     * @since 1.16.0
+     */
+    @Nullable
+    public SocketAddress createSocketAddress(
+        SocketAddress remoteAddress, @EquivalentAddressGroup.Attr Attributes attrs) {
+      return null;
+    }
+  }
+
   @Override
   @CheckReturnValue
   @Internal
   protected ClientTransportFactory buildTransportFactory() {
-    TransportCreationParamsFilterFactory transportCreationParamsFilterFactory =
-        dynamicParamsFactory;
-    if (transportCreationParamsFilterFactory == null) {
+    ProtocolNegotiator negotiator;
+    if (protocolNegotiatorFactory != null) {
+      negotiator = protocolNegotiatorFactory.buildProtocolNegotiator();
+    } else {
       SslContext localSslContext = sslContext;
       if (negotiationType == NegotiationType.TLS && localSslContext == null) {
         try {
@@ -342,16 +379,13 @@
           throw new RuntimeException(ex);
         }
       }
-      ProtocolNegotiator negotiator =
-          createProtocolNegotiatorByType(negotiationType, localSslContext);
-      transportCreationParamsFilterFactory =
-          new DefaultNettyTransportCreationParamsFilterFactory(negotiator);
+      negotiator = createProtocolNegotiatorByType(negotiationType, localSslContext);
     }
     return new NettyTransportFactory(
-        transportCreationParamsFilterFactory, channelType, channelOptions,
+        negotiator, channelType, channelOptions,
         eventLoopGroup, flowControlWindow, maxInboundMessageSize(),
         maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
-        transportTracerFactory.create());
+        transportTracerFactory.create(), localSocketPicker);
   }
 
   @Override
@@ -409,8 +443,9 @@
     return super.checkAuthority(authority);
   }
 
-  void setDynamicParamsFactory(TransportCreationParamsFilterFactory factory) {
-    this.dynamicParamsFactory = checkNotNull(factory, "factory");
+  void protocolNegotiatorFactory(ProtocolNegotiatorFactory protocolNegotiatorFactory) {
+    this.protocolNegotiatorFactory
+        = Preconditions.checkNotNull(protocolNegotiatorFactory, "protocolNegotiatorFactory");
   }
 
   @Override
@@ -434,24 +469,12 @@
     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();
+  interface ProtocolNegotiatorFactory {
+    /**
+     * Returns a ProtocolNegotatior instance configured for this Builder. This method is called
+     * during {@code ManagedChannelBuilder#build()}.
+     */
+    ProtocolNegotiator buildProtocolNegotiator();
   }
 
   /**
@@ -459,7 +482,7 @@
    */
   @CheckReturnValue
   private static final class NettyTransportFactory implements ClientTransportFactory {
-    private final TransportCreationParamsFilterFactory transportCreationParamsFilterFactory;
+    private final ProtocolNegotiator protocolNegotiator;
     private final Class<? extends Channel> channelType;
     private final Map<ChannelOption<?>, ?> channelOptions;
     private final EventLoopGroup group;
@@ -471,15 +494,16 @@
     private final long keepAliveTimeoutNanos;
     private final boolean keepAliveWithoutCalls;
     private final TransportTracer transportTracer;
+    private final LocalSocketPicker localSocketPicker;
 
     private boolean closed;
 
-    NettyTransportFactory(TransportCreationParamsFilterFactory transportCreationParamsFilterFactory,
+    NettyTransportFactory(ProtocolNegotiator protocolNegotiator,
         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;
+        TransportTracer transportTracer, LocalSocketPicker localSocketPicker) {
+      this.protocolNegotiator = protocolNegotiator;
       this.channelType = channelType;
       this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
       this.flowControlWindow = flowControlWindow;
@@ -489,6 +513,8 @@
       this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
       this.keepAliveWithoutCalls = keepAliveWithoutCalls;
       this.transportTracer = transportTracer;
+      this.localSocketPicker =
+          localSocketPicker != null ? localSocketPicker : new LocalSocketPicker();
 
       usingSharedGroup = group == null;
       if (usingSharedGroup) {
@@ -504,12 +530,13 @@
         SocketAddress serverAddress, ClientTransportOptions options) {
       checkState(!closed, "The transport factory is closed.");
 
-      TransportCreationParamsFilter dparams =
-          transportCreationParamsFilterFactory.create(
-              serverAddress,
-              options.getAuthority(),
-              options.getUserAgent(),
-              options.getProxyParameters());
+      ProtocolNegotiator localNegotiator = protocolNegotiator;
+      ProxyParameters proxyParams = options.getProxyParameters();
+      if (proxyParams != null) {
+        localNegotiator = ProtocolNegotiators.httpProxy(
+            proxyParams.proxyAddress, proxyParams.username, proxyParams.password,
+            protocolNegotiator);
+      }
 
       final AtomicBackoff.State keepAliveTimeNanosState = keepAliveTimeNanos.getState();
       Runnable tooManyPingsRunnable = new Runnable() {
@@ -518,12 +545,14 @@
           keepAliveTimeNanosState.backoff();
         }
       };
+
       NettyClientTransport transport = new NettyClientTransport(
-          dparams.getTargetServerAddress(), channelType, channelOptions, group,
-          dparams.getProtocolNegotiator(), flowControlWindow,
+          serverAddress, channelType, channelOptions, group,
+          localNegotiator, flowControlWindow,
           maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
-          keepAliveWithoutCalls, dparams.getAuthority(), dparams.getUserAgent(),
-          tooManyPingsRunnable, transportTracer, options.getEagAttributes());
+          keepAliveWithoutCalls, options.getAuthority(), options.getUserAgent(),
+          tooManyPingsRunnable, transportTracer, options.getEagAttributes(),
+          localSocketPicker);
       return transport;
     }
 
@@ -539,73 +568,10 @@
       }
       closed = true;
 
+      protocolNegotiator.close();
       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/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java
index 6141db2..c1cc078 100644
--- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java
+++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java
@@ -40,6 +40,7 @@
 import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger;
 import io.grpc.internal.StatsTraceContext;
 import io.grpc.internal.TransportTracer;
+import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
@@ -64,7 +65,7 @@
 class NettyClientTransport implements ConnectionClientTransport {
   private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
   private final Map<ChannelOption<?>, ?> channelOptions;
-  private final SocketAddress address;
+  private final SocketAddress remoteAddress;
   private final Class<? extends Channel> channelType;
   private final EventLoopGroup group;
   private final ProtocolNegotiator negotiator;
@@ -91,6 +92,7 @@
   /** Since not thread-safe, may only be used from event loop. */
   private final TransportTracer transportTracer;
   private final Attributes eagAttributes;
+  private final LocalSocketPicker localSocketPicker;
 
   NettyClientTransport(
       SocketAddress address, Class<? extends Channel> channelType,
@@ -98,9 +100,10 @@
       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) {
+      Runnable tooManyPingsRunnable, TransportTracer transportTracer, Attributes eagAttributes,
+      LocalSocketPicker localSocketPicker) {
     this.negotiator = Preconditions.checkNotNull(negotiator, "negotiator");
-    this.address = Preconditions.checkNotNull(address, "address");
+    this.remoteAddress = Preconditions.checkNotNull(address, "address");
     this.group = Preconditions.checkNotNull(group, "group");
     this.channelType = Preconditions.checkNotNull(channelType, "channelType");
     this.channelOptions = Preconditions.checkNotNull(channelOptions, "channelOptions");
@@ -117,6 +120,7 @@
         Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
     this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
     this.eagAttributes = Preconditions.checkNotNull(eagAttributes, "eagAttributes");
+    this.localSocketPicker = Preconditions.checkNotNull(localSocketPicker, "localSocketPicker");
   }
 
   @Override
@@ -263,7 +267,13 @@
       }
     });
     // Start the connection operation to the server.
-    channel.connect(address);
+    SocketAddress localAddress =
+        localSocketPicker.createSocketAddress(remoteAddress, eagAttributes);
+    if (localAddress != null) {
+      channel.connect(remoteAddress, localAddress);
+    } else {
+      channel.connect(remoteAddress);
+    }
 
     if (keepAliveManager != null) {
       keepAliveManager.onTransportStarted();
@@ -305,7 +315,7 @@
   public String toString() {
     return MoreObjects.toStringHelper(this)
         .add("logId", logId.getId())
-        .add("address", address)
+        .add("remoteAddress", remoteAddress)
         .add("channel", channel)
         .toString();
   }
diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java
index 132e96a..098ba73 100644
--- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java
+++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java
@@ -43,4 +43,11 @@
    * completed successfully.
    */
   Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler);
+
+  /**
+   * Releases resources held by this negotiator. Called when the Channel transitions to terminated.
+   * Is currently only supported on client-side; server-side protocol negotiators will not see this
+   * method called.
+   */
+  void close();
 }
diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
index 3a13207..0618017 100644
--- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
+++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
@@ -22,12 +22,12 @@
 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.GrpcAttributes;
 import io.grpc.internal.GrpcUtil;
 import io.netty.channel.ChannelDuplexHandler;
 import io.netty.channel.ChannelFuture;
@@ -90,6 +90,7 @@
             // 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())
+                .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
                 .build(),
                 /*securityInfo=*/ null);
             // Just replace this handler with the gRPC handler.
@@ -104,6 +105,9 @@
 
         return new PlaintextHandler();
       }
+
+      @Override
+      public void close() {}
     };
   }
 
@@ -117,6 +121,9 @@
       public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
         return new ServerTlsHandler(sslContext, handler);
       }
+
+      @Override
+      public void close() {}
     };
   }
 
@@ -157,6 +164,7 @@
                 Attributes.newBuilder()
                     .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session)
                     .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                    .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
                     .build(),
                 new InternalChannelz.Security(new InternalChannelz.Tls(session)));
             // Replace this handler with the GRPC handler.
@@ -207,6 +215,13 @@
         return new BufferUntilProxyTunnelledHandler(
             proxyHandler, negotiator.newHandler(http2Handler));
       }
+
+      // This method is not normally called, because we use httpProxy on a per-connection basis in
+      // NettyChannelBuilder. Instead, we expect `negotiator' to be closed by NettyTransportFactory.
+      @Override
+      public void close() {
+        negotiator.close();
+      }
     }
 
     return new ProxyNegotiator();
@@ -310,6 +325,9 @@
       };
       return new BufferUntilTlsNegotiatedHandler(sslBootstrap, handler);
     }
+
+    @Override
+    public void close() {}
   }
 
   /** A tuple of (host, port). */
@@ -341,6 +359,9 @@
           new HttpClientUpgradeHandler(httpClientCodec, upgradeCodec, 1000);
       return new BufferingHttp2UpgradeHandler(upgrader, handler);
     }
+
+    @Override
+    public void close() {}
   }
 
   /**
@@ -357,6 +378,9 @@
     public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
       return new BufferUntilChannelActiveHandler(handler);
     }
+
+    @Override
+    public void close() {}
   }
 
   private static RuntimeException unavailableException(String msg) {
@@ -651,7 +675,8 @@
                 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)
+                    .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
+                    .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
                     .build(),
                 new InternalChannelz.Security(new InternalChannelz.Tls(session)));
             writeBufferedAndRemove(ctx);
@@ -699,7 +724,8 @@
           Attributes
               .newBuilder()
               .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
-              .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+              .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
+              .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
               .build(),
           /*securityInfo=*/ null);
       super.channelActive(ctx);
@@ -742,7 +768,8 @@
             Attributes
                 .newBuilder()
                 .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
-                .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+                .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
+                .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
                 .build(),
             /*securityInfo=*/ null);
       } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_REJECTED) {
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java
index 682af1b..7b28fac 100644
--- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java
@@ -61,6 +61,7 @@
 import io.grpc.internal.ServerTransportListener;
 import io.grpc.internal.TransportTracer;
 import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker;
 import io.netty.channel.ChannelConfig;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.nio.NioEventLoopGroup;
@@ -77,14 +78,17 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetSocketAddress;
+import java.net.SocketAddress;
 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.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import javax.annotation.Nullable;
 import javax.net.ssl.SSLHandshakeException;
 import org.junit.After;
 import org.junit.Before;
@@ -105,6 +109,8 @@
   private ManagedClientTransport.Listener clientTransportListener;
 
   private final List<NettyClientTransport> transports = new ArrayList<>();
+  private final LinkedBlockingQueue<Attributes> serverTransportAttributesList =
+      new LinkedBlockingQueue<>();
   private final NioEventLoopGroup group = new NioEventLoopGroup(1);
   private final EchoServerListener serverListener = new EchoServerListener();
   private final InternalChannelz channelz = new InternalChannelz();
@@ -175,7 +181,7 @@
         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);
+        tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY, new SocketPicker());
     transports.add(transport);
     callMeMaybe(transport.start(clientTransportListener));
 
@@ -415,7 +421,7 @@
         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);
+        null, tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY, new SocketPicker());
     transports.add(transport);
 
     // Should not throw
@@ -536,6 +542,11 @@
 
     assertNotNull(rpc.stream.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION));
     assertEquals(address, rpc.stream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
+    Attributes serverTransportAttrs = serverTransportAttributesList.poll(1, TimeUnit.SECONDS);
+    assertNotNull(serverTransportAttrs);
+    SocketAddress clientAddr = serverTransportAttrs.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+    assertNotNull(clientAddr);
+    assertEquals(clientAddr, rpc.stream.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR));
   }
 
   @Test
@@ -593,7 +604,7 @@
         DEFAULT_WINDOW_SIZE, maxMsgSize, maxHeaderListSize,
         keepAliveTimeNano, keepAliveTimeoutNano,
         false, authority, userAgent, tooManyPingsRunnable,
-        new TransportTracer(), eagAttributes);
+        new TransportTracer(), eagAttributes, new SocketPicker());
     transports.add(transport);
     return transport;
   }
@@ -749,7 +760,7 @@
     }
   }
 
-  private static final class EchoServerListener implements ServerListener {
+  private final class EchoServerListener implements ServerListener {
     final List<NettyServerTransport> transports = new ArrayList<>();
     final List<EchoServerStreamListener> streamListeners =
             Collections.synchronizedList(new ArrayList<EchoServerStreamListener>());
@@ -769,6 +780,7 @@
 
         @Override
         public Attributes transportReady(Attributes transportAttrs) {
+          serverTransportAttributesList.add(transportAttrs);
           return transportAttrs;
         }
 
@@ -821,5 +833,17 @@
       this.grpcHandler = grpcHandler;
       return handler = new NoopHandler(grpcHandler);
     }
+
+    @Override
+    public void close() {}
+  }
+
+  private static final class SocketPicker extends LocalSocketPicker {
+
+    @Nullable
+    @Override
+    public SocketAddress createSocketAddress(SocketAddress remoteAddress, Attributes attrs) {
+      return null;
+    }
   }
 }
diff --git a/okhttp/Android.bp b/okhttp/Android.bp
new file mode 100644
index 0000000..8193d9b
--- /dev/null
+++ b/okhttp/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 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.
+//
+
+java_library_host {
+    name: "grpc-java-okhttp",
+    srcs: [
+        "third_party/okhttp/main/java/**/*.java",
+        "src/main/java/**/*.java",
+    ],
+    java_resource_dirs: [
+        "src/main/resources",
+    ],
+    libs: [
+        "grpc-java-core",
+        "grpc-java-core-internal",
+        "jsr305",
+        "guava",
+    ],
+    static_libs: [
+        "okhttp",
+    ],
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java b/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java
index 3049c5b..210aaa1 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java
@@ -16,6 +16,7 @@
 
 package io.grpc.okhttp;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import io.grpc.internal.SerializingExecutor;
 import io.grpc.okhttp.internal.framed.ErrorCode;
@@ -24,7 +25,11 @@
 import io.grpc.okhttp.internal.framed.Settings;
 import java.io.IOException;
 import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -39,6 +44,9 @@
   private final SerializingExecutor executor;
   private final TransportExceptionHandler transportExceptionHandler;
   private final AtomicLong flushCounter = new AtomicLong();
+  // Some exceptions are not very useful and add too much noise to the log
+  private static final Set<String> QUIET_ERRORS =
+      Collections.unmodifiableSet(new HashSet<>(Arrays.asList("Socket closed")));
 
   public AsyncFrameWriter(
       TransportExceptionHandler transportExceptionHandler, SerializingExecutor executor) {
@@ -213,13 +221,28 @@
             frameWriter.close();
             socket.close();
           } catch (IOException e) {
-            log.log(Level.WARNING, "Failed closing connection", e);
+            log.log(getLogLevel(e), "Failed closing connection", e);
           }
         }
       }
     });
   }
 
+  /**
+   * 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
+        && QUIET_ERRORS.contains(t.getMessage())) {
+      return Level.FINE;
+
+    }
+    return Level.INFO;
+  }
+
   private abstract class WriteRunnable implements Runnable {
     @Override
     public final void run() {
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
index 9bca807..aa9e074 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
@@ -31,7 +31,6 @@
 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;
@@ -46,6 +45,7 @@
 import io.grpc.StatusException;
 import io.grpc.internal.ClientStreamListener.RpcProgress;
 import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcAttributes;
 import io.grpc.internal.GrpcUtil;
 import io.grpc.internal.Http2Ping;
 import io.grpc.internal.KeepAliveManager;
@@ -484,8 +484,9 @@
           attributes = Attributes
               .newBuilder()
               .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, sock.getRemoteSocketAddress())
+              .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, sock.getLocalSocketAddress())
               .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession)
-              .set(CallCredentials.ATTR_SECURITY_LEVEL,
+              .set(GrpcAttributes.ATTR_SECURITY_LEVEL,
                   sslSession == null ? SecurityLevel.NONE : SecurityLevel.PRIVACY_AND_INTEGRITY)
               .build();
         } catch (StatusException e) {
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
index 1858a93..eabe385 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
@@ -93,7 +93,7 @@
 
       String negotiatedProtocol = getSelectedProtocol(sslSocket);
       if (negotiatedProtocol == null) {
-        throw new RuntimeException("protocol negotiation failed");
+        throw new RuntimeException("TLS ALPN negotiation failed with protocols: " + protocols);
       }
       return negotiatedProtocol;
     } finally {
@@ -185,6 +185,7 @@
             return new String(alpnResult, Util.UTF_8);
           }
         } catch (Exception e) {
+          logger.log(Level.FINE, "Failed calling getAlpnSelectedProtocol()", e);
           // In some implementations, querying selected protocol before the handshake will fail with
           // exception.
         }
@@ -198,6 +199,7 @@
             return new String(npnResult, Util.UTF_8);
           }
         } catch (Exception e) {
+          logger.log(Level.FINE, "Failed calling getNpnSelectedProtocol()", e);
           // In some implementations, querying selected protocol before the handshake will fail with
           // exception.
         }
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
index 0a8672c..e43713d 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
@@ -43,7 +43,7 @@
    */
   @VisibleForTesting
   static final List<Protocol> TLS_PROTOCOLS =
-      Collections.unmodifiableList(Arrays.<Protocol>asList(Protocol.GRPC_EXP, Protocol.HTTP_2));
+      Collections.unmodifiableList(Arrays.asList(Protocol.GRPC_EXP, Protocol.HTTP_2));
 
   /**
    * Upgrades given Socket to be a SSLSocket.
diff --git a/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java b/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java
index 479a35a..45e8070 100644
--- a/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java
+++ b/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java
@@ -16,11 +16,13 @@
 
 package io.grpc.okhttp;
 
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.okhttp.AsyncFrameWriter.getLogLevel;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
 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;
@@ -30,6 +32,7 @@
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
+import java.util.logging.Level;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -92,6 +95,28 @@
     inOrder.verify(frameWriter).flush();
   }
 
+  @Test
+  public void unknownException() {
+    assertThat(getLogLevel(new Exception())).isEqualTo(Level.INFO);
+  }
+
+  @Test
+  public void quiet() {
+    assertThat(getLogLevel(new IOException("Socket closed"))).isEqualTo(Level.FINE);
+  }
+
+  @Test
+  public void nonquiet() {
+    assertThat(getLogLevel(new IOException("foo"))).isEqualTo(Level.INFO);
+  }
+
+  @Test
+  public void nullMessage() {
+    IOException e = new IOException();
+    assertThat(e.getMessage()).isNull();
+    assertThat(getLogLevel(e)).isEqualTo(Level.INFO);
+  }
+
   /**
    * Executor queues incoming runnables instead of running it. Runnables can be invoked via {@link
    * QueueingExecutor#runAll} in serial order.
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java
index 5f183dc..67d6782 100644
--- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java
@@ -128,7 +128,7 @@
     OkHttpProtocolNegotiator negotiator = new OkHttpProtocolNegotiator(platform);
 
     thrown.expect(RuntimeException.class);
-    thrown.expectMessage("protocol negotiation failed");
+    thrown.expectMessage("TLS ALPN negotiation failed");
 
     negotiator.negotiate(sock, "hostname", ImmutableList.of(Protocol.HTTP_2));
   }
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
index 51244c9..2610932 100644
--- 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
@@ -89,7 +89,8 @@
         "com.google.android.gms.org.conscrypt.OpenSSLProvider",
         "org.conscrypt.OpenSSLProvider",
         "com.android.org.conscrypt.OpenSSLProvider",
-        "org.apache.harmony.xnet.provider.jsse.OpenSSLProvider"
+        "org.apache.harmony.xnet.provider.jsse.OpenSSLProvider",
+        "com.google.android.libraries.stitch.sslguard.SslGuardProvider"
       };
 
   private static final Platform PLATFORM = findPlatform();
@@ -185,7 +186,8 @@
       if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
         tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
       } else if (androidOrAppEngineProvider.getName().equals("GmsCore_OpenSSL")
-          || androidOrAppEngineProvider.getName().equals("Conscrypt")) {
+          || androidOrAppEngineProvider.getName().equals("Conscrypt")
+          || androidOrAppEngineProvider.getName().equals("Ssl_Guard")) {
         tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
       } else if (isAtLeastAndroid5()) {
         tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
diff --git a/protobuf-lite/Android.bp b/protobuf-lite/Android.bp
new file mode 100644
index 0000000..a8374f5
--- /dev/null
+++ b/protobuf-lite/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 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.
+//
+//
+
+java_library_host {
+    name: "grpc-java-protobuf-lite",
+    srcs: [
+        "src/main/java/**/*.java",
+    ],
+    libs: [
+        "grpc-java-core",
+        "guava",
+        "jsr305",
+        "libprotobuf-java-full",
+    ],
+}
diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle
index ead5180..0cd67a9 100644
--- a/protobuf-lite/build.gradle
+++ b/protobuf-lite/build.gradle
@@ -13,8 +13,15 @@
 
 dependencies {
     compile project(':grpc-core'),
-            libraries.protobuf_lite,
-            libraries.guava
+            libraries.protobuf_lite
+    compile (libraries.guava) {
+        // prefer 2.2.0 from libraries instead of 2.1.3
+        exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
+        // prefer 3.0.2 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 1.17 from libraries instead of 1.14
+        exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
+    }
 
     testProtobuf libraries.protobuf
 
diff --git a/protobuf-nano/build.gradle b/protobuf-nano/build.gradle
index dd26db4..aae0a79 100644
--- a/protobuf-nano/build.gradle
+++ b/protobuf-nano/build.gradle
@@ -11,8 +11,15 @@
 
 dependencies {
     compile project(':grpc-core'),
-            libraries.protobuf_nano,
-            libraries.guava
+            libraries.protobuf_nano
+    compile (libraries.guava) {
+        // prefer 2.2.0 from libraries instead of 2.1.3
+        exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
+        // prefer 3.0.2 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 1.17 from libraries instead of 1.14
+        exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
+    }
     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/protobuf/Android.bp b/protobuf/Android.bp
index cec1d76..3e1d243 100644
--- a/protobuf/Android.bp
+++ b/protobuf/Android.bp
@@ -13,10 +13,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-// TODO: Build from source instead
-java_import_host {
+//
+
+java_library_host {
     name: "grpc-java-protobuf",
-    jars: [
-        "grpc-protobuf-1.14.0.jar",
+    srcs: [
+        "src/main/java/**/*.java",
+    ],
+    libs: [
+        "grpc-java-core",
+        "grpc-java-protobuf-lite",
+        "com.google.api.grpc_proto-google-common-protos-prebuilt-jar",
+        "jsr305",
+        "guava",
+        "libprotobuf-java-full",
+        "libprotobuf-java-util-full",
     ],
 }
diff --git a/protobuf/build.gradle b/protobuf/build.gradle
index abb2bc7..31f4f3c 100644
--- a/protobuf/build.gradle
+++ b/protobuf/build.gradle
@@ -10,8 +10,15 @@
 
 dependencies {
     compile project(':grpc-core'),
-            libraries.protobuf,
-            libraries.guava
+            libraries.protobuf
+    compile (libraries.guava) {
+        // prefer 2.2.0 from libraries instead of 2.1.3
+        exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
+        // prefer 3.0.2 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 1.17 from libraries instead of 1.14
+        exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
+    }
 
     compile (libraries.google_api_protos) {
         // 'com.google.api:api-common' transitively depends on auto-value, which breaks our
diff --git a/protobuf/grpc-protobuf-1.14.0.jar b/protobuf/grpc-protobuf-1.14.0.jar
deleted file mode 100644
index c9bad69..0000000
--- a/protobuf/grpc-protobuf-1.14.0.jar
+++ /dev/null
Binary files differ
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
index a230d50..c936b3c 100644
--- a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
@@ -16,6 +16,7 @@
 
 package io.grpc.protobuf;
 
+import com.google.protobuf.ExtensionRegistry;
 import com.google.protobuf.Message;
 import io.grpc.ExperimentalApi;
 import io.grpc.Metadata;
@@ -28,6 +29,26 @@
 public final class ProtoUtils {
 
   /**
+   * 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.16.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1787")
+  public static void setExtensionRegistry(ExtensionRegistry registry) {
+    ProtoLiteUtils.setExtensionRegistry(registry);
+  }
+
+  /**
    * Create a {@link Marshaller} for protos of the same type as {@code defaultInstance}.
    *
    * @since 1.0.0
diff --git a/repositories.bzl b/repositories.bzl
index c3bbdfd..a83bd7a 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -132,8 +132,8 @@
 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",
+        artifact = "com.google.code.findbugs:jsr305:3.0.2",
+        sha1 = "25ea2e8b0c338a877313bd4672d3fe056ea78f0d",
     )
 
 def com_google_code_gson():
@@ -153,8 +153,8 @@
 def com_google_guava():
     native.maven_jar(
         name = "com_google_guava_guava",
-        artifact = "com.google.guava:guava:20.0",
-        sha1 = "89507701249388e1ed5ddcf8c41f4ce1be7831ef",
+        artifact = "com.google.guava:guava:26.0-android",
+        sha1 = "ef69663836b339db335fde0df06fb3cd84e3742b",
     )
 
 def com_google_protobuf():
@@ -212,81 +212,81 @@
         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",
+        artifact = "io.netty:netty-buffer:4.1.30.Final",
+        sha1 = "597adb653306470fb3ec1af3c0f3f30a37b1310a",
     )
 
 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",
+        artifact = "io.netty:netty-codec:4.1.30.Final",
+        sha1 = "515c8f609aaca28a94f984d89a9667dd3359c1b1",
     )
 
 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",
+        artifact = "io.netty:netty-codec-http:4.1.30.Final",
+        sha1 = "1384c630e8a0eeef33ad12a28791dce6e1d8767c",
+    )
+
+def io_netty_codec_http2():
+    native.maven_jar(
+        name = "io_netty_netty_codec_http2",
+        artifact = "io.netty:netty-codec-http2:4.1.30.Final",
+        sha1 = "2da92f518409904954d3e8dcc42eb6a562a70302",
+    )
+
+def io_netty_codec_socks():
+    native.maven_jar(
+        name = "io_netty_netty_codec_socks",
+        artifact = "io.netty:netty-codec-socks:4.1.30.Final",
+        sha1 = "ea272e3bb281d3a91d27278f47e61b4de285cc27",
+    )
+
+def io_netty_common():
+    native.maven_jar(
+        name = "io_netty_netty_common",
+        artifact = "io.netty:netty-common:4.1.30.Final",
+        sha1 = "5dca0c34d8f38af51a2398614e81888f51cf811a",
     )
 
 def io_netty_handler():
     native.maven_jar(
         name = "io_netty_netty_handler",
-        artifact = "io.netty:netty-handler:4.1.27.Final",
-        sha1 = "21bd9cf565390a8d72579b8664303e3c175dfc6a",
+        artifact = "io.netty:netty-handler:4.1.30.Final",
+        sha1 = "ecc076332ed103411347f4806a44ee32d9d9cb5f",
     )
 
 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",
+        artifact = "io.netty:netty-handler-proxy:4.1.30.Final",
+        sha1 = "1baa1568fa936caddca0fae96fdf127fd5cbad16",
     )
 
 def io_netty_resolver():
     native.maven_jar(
         name = "io_netty_netty_resolver",
-        artifact = "io.netty:netty-resolver:4.1.27.Final",
-        sha1 = "2536447ef9605ccb2b5203aa22392c6514484ea9",
+        artifact = "io.netty:netty-resolver:4.1.30.Final",
+        sha1 = "5106fd687066ffd712e5295d32af4e2ac6482613",
     )
 
 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",
+        artifact = "io.netty:netty-tcnative-boringssl-static:2.0.17.Final",
+        sha1 = "b1e5acbde8c444c656131238ac6ab9e73f694300",
+    )
+
+def io_netty_transport():
+    native.maven_jar(
+        name = "io_netty_netty_transport",
+        artifact = "io.netty:netty-transport:4.1.30.Final",
+        sha1 = "3d27bb432a3b125167ac161b26415ad29ec17f02",
     )
 
 def io_opencensus_api():
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
index 295a747..4a4f9fb 100644
--- a/services/src/generated/main/grpc/io/grpc/health/v1/HealthGrpc.java
+++ b/services/src/generated/main/grpc/io/grpc/health/v1/HealthGrpc.java
@@ -59,6 +59,38 @@
      return getCheckMethod;
   }
 
+  private static volatile io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest,
+      io.grpc.health.v1.HealthCheckResponse> getWatchMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Watch",
+      requestType = io.grpc.health.v1.HealthCheckRequest.class,
+      responseType = io.grpc.health.v1.HealthCheckResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest,
+      io.grpc.health.v1.HealthCheckResponse> getWatchMethod() {
+    io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest, io.grpc.health.v1.HealthCheckResponse> getWatchMethod;
+    if ((getWatchMethod = HealthGrpc.getWatchMethod) == null) {
+      synchronized (HealthGrpc.class) {
+        if ((getWatchMethod = HealthGrpc.getWatchMethod) == null) {
+          HealthGrpc.getWatchMethod = getWatchMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.health.v1.HealthCheckRequest, io.grpc.health.v1.HealthCheckResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.health.v1.Health", "Watch"))
+              .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("Watch"))
+                  .build();
+          }
+        }
+     }
+     return getWatchMethod;
+  }
+
   /**
    * Creates a new async stub that supports all call types for the service
    */
@@ -87,12 +119,38 @@
   public static abstract class HealthImplBase implements io.grpc.BindableService {
 
     /**
+     * <pre>
+     * If the requested service is unknown, the call will fail with status
+     * NOT_FOUND.
+     * </pre>
      */
     public void check(io.grpc.health.v1.HealthCheckRequest request,
         io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse> responseObserver) {
       asyncUnimplementedUnaryCall(getCheckMethod(), responseObserver);
     }
 
+    /**
+     * <pre>
+     * Performs a watch for the serving status of the requested service.
+     * The server will immediately send back a message indicating the current
+     * serving status.  It will then subsequently send a new message whenever
+     * the service's serving status changes.
+     * If the requested service is unknown when the call is received, the
+     * server will send a message setting the serving status to
+     * SERVICE_UNKNOWN but will *not* terminate the call.  If at some
+     * future point, the serving status of the service becomes known, the
+     * server will send a new message with the service's serving status.
+     * If the call terminates with status UNIMPLEMENTED, then clients
+     * should assume this method is not supported and should not retry the
+     * call.  If the call terminates with any other status (including OK),
+     * clients should retry the call with appropriate exponential backoff.
+     * </pre>
+     */
+    public void watch(io.grpc.health.v1.HealthCheckRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getWatchMethod(), responseObserver);
+    }
+
     @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
       return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
           .addMethod(
@@ -102,6 +160,13 @@
                 io.grpc.health.v1.HealthCheckRequest,
                 io.grpc.health.v1.HealthCheckResponse>(
                   this, METHODID_CHECK)))
+          .addMethod(
+            getWatchMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.health.v1.HealthCheckRequest,
+                io.grpc.health.v1.HealthCheckResponse>(
+                  this, METHODID_WATCH)))
           .build();
     }
   }
@@ -125,12 +190,39 @@
     }
 
     /**
+     * <pre>
+     * If the requested service is unknown, the call will fail with status
+     * NOT_FOUND.
+     * </pre>
      */
     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);
     }
+
+    /**
+     * <pre>
+     * Performs a watch for the serving status of the requested service.
+     * The server will immediately send back a message indicating the current
+     * serving status.  It will then subsequently send a new message whenever
+     * the service's serving status changes.
+     * If the requested service is unknown when the call is received, the
+     * server will send a message setting the serving status to
+     * SERVICE_UNKNOWN but will *not* terminate the call.  If at some
+     * future point, the serving status of the service becomes known, the
+     * server will send a new message with the service's serving status.
+     * If the call terminates with status UNIMPLEMENTED, then clients
+     * should assume this method is not supported and should not retry the
+     * call.  If the call terminates with any other status (including OK),
+     * clients should retry the call with appropriate exponential backoff.
+     * </pre>
+     */
+    public void watch(io.grpc.health.v1.HealthCheckRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getWatchMethod(), getCallOptions()), request, responseObserver);
+    }
   }
 
   /**
@@ -152,11 +244,38 @@
     }
 
     /**
+     * <pre>
+     * If the requested service is unknown, the call will fail with status
+     * NOT_FOUND.
+     * </pre>
      */
     public io.grpc.health.v1.HealthCheckResponse check(io.grpc.health.v1.HealthCheckRequest request) {
       return blockingUnaryCall(
           getChannel(), getCheckMethod(), getCallOptions(), request);
     }
+
+    /**
+     * <pre>
+     * Performs a watch for the serving status of the requested service.
+     * The server will immediately send back a message indicating the current
+     * serving status.  It will then subsequently send a new message whenever
+     * the service's serving status changes.
+     * If the requested service is unknown when the call is received, the
+     * server will send a message setting the serving status to
+     * SERVICE_UNKNOWN but will *not* terminate the call.  If at some
+     * future point, the serving status of the service becomes known, the
+     * server will send a new message with the service's serving status.
+     * If the call terminates with status UNIMPLEMENTED, then clients
+     * should assume this method is not supported and should not retry the
+     * call.  If the call terminates with any other status (including OK),
+     * clients should retry the call with appropriate exponential backoff.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.health.v1.HealthCheckResponse> watch(
+        io.grpc.health.v1.HealthCheckRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getWatchMethod(), getCallOptions(), request);
+    }
   }
 
   /**
@@ -178,6 +297,10 @@
     }
 
     /**
+     * <pre>
+     * If the requested service is unknown, the call will fail with status
+     * NOT_FOUND.
+     * </pre>
      */
     public com.google.common.util.concurrent.ListenableFuture<io.grpc.health.v1.HealthCheckResponse> check(
         io.grpc.health.v1.HealthCheckRequest request) {
@@ -187,6 +310,7 @@
   }
 
   private static final int METHODID_CHECK = 0;
+  private static final int METHODID_WATCH = 1;
 
   private static final class MethodHandlers<Req, Resp> implements
       io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
@@ -209,6 +333,10 @@
           serviceImpl.check((io.grpc.health.v1.HealthCheckRequest) request,
               (io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse>) responseObserver);
           break;
+        case METHODID_WATCH:
+          serviceImpl.watch((io.grpc.health.v1.HealthCheckRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse>) responseObserver);
+          break;
         default:
           throw new AssertionError();
       }
@@ -271,6 +399,7 @@
           serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
               .setSchemaDescriptor(new HealthFileDescriptorSupplier())
               .addMethod(getCheckMethod())
+              .addMethod(getWatchMethod())
               .build();
         }
       }
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/Address.java b/services/src/generated/main/java/io/grpc/binarylog/v1/Address.java
new file mode 100644
index 0000000..c4c64a4
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/Address.java
@@ -0,0 +1,823 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * <pre>
+ * Address information
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1.Address}
+ */
+public  final class Address extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.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() {
+    type_ = 0;
+    address_ = "";
+    ipPort_ = 0;
+  }
+
+  @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 8: {
+            int rawValue = input.readEnum();
+
+            type_ = rawValue;
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            address_ = s;
+            break;
+          }
+          case 24: {
+
+            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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Address_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Address_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.Address.class, io.grpc.binarylog.v1.Address.Builder.class);
+  }
+
+  /**
+   * Protobuf enum {@code grpc.binarylog.v1.Address.Type}
+   */
+  public enum Type
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>TYPE_UNKNOWN = 0;</code>
+     */
+    TYPE_UNKNOWN(0),
+    /**
+     * <pre>
+     * address is in 1.2.3.4 form
+     * </pre>
+     *
+     * <code>TYPE_IPV4 = 1;</code>
+     */
+    TYPE_IPV4(1),
+    /**
+     * <pre>
+     * address is in IPv6 canonical form (RFC5952 section 4)
+     * The scope is NOT included in the address string.
+     * </pre>
+     *
+     * <code>TYPE_IPV6 = 2;</code>
+     */
+    TYPE_IPV6(2),
+    /**
+     * <pre>
+     * address is UDS string
+     * </pre>
+     *
+     * <code>TYPE_UNIX = 3;</code>
+     */
+    TYPE_UNIX(3),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>TYPE_UNKNOWN = 0;</code>
+     */
+    public static final int TYPE_UNKNOWN_VALUE = 0;
+    /**
+     * <pre>
+     * address is in 1.2.3.4 form
+     * </pre>
+     *
+     * <code>TYPE_IPV4 = 1;</code>
+     */
+    public static final int TYPE_IPV4_VALUE = 1;
+    /**
+     * <pre>
+     * address is in IPv6 canonical form (RFC5952 section 4)
+     * The scope is NOT included in the address string.
+     * </pre>
+     *
+     * <code>TYPE_IPV6 = 2;</code>
+     */
+    public static final int TYPE_IPV6_VALUE = 2;
+    /**
+     * <pre>
+     * address is UDS string
+     * </pre>
+     *
+     * <code>TYPE_UNIX = 3;</code>
+     */
+    public static final int TYPE_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 Type valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static Type forNumber(int value) {
+      switch (value) {
+        case 0: return TYPE_UNKNOWN;
+        case 1: return TYPE_IPV4;
+        case 2: return TYPE_IPV6;
+        case 3: return TYPE_UNIX;
+        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.v1.Address.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.v1.Address.Type)
+  }
+
+  public static final int TYPE_FIELD_NUMBER = 1;
+  private int type_;
+  /**
+   * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+   */
+  public int getTypeValue() {
+    return type_;
+  }
+  /**
+   * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+   */
+  public io.grpc.binarylog.v1.Address.Type getType() {
+    io.grpc.binarylog.v1.Address.Type result = io.grpc.binarylog.v1.Address.Type.valueOf(type_);
+    return result == null ? io.grpc.binarylog.v1.Address.Type.UNRECOGNIZED : result;
+  }
+
+  public static final int ADDRESS_FIELD_NUMBER = 2;
+  private volatile java.lang.Object address_;
+  /**
+   * <code>string address = 2;</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 = 2;</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 = 3;
+  private int ipPort_;
+  /**
+   * <pre>
+   * only for TYPE_IPV4 and TYPE_IPV6
+   * </pre>
+   *
+   * <code>uint32 ip_port = 3;</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 (type_ != io.grpc.binarylog.v1.Address.Type.TYPE_UNKNOWN.getNumber()) {
+      output.writeEnum(1, type_);
+    }
+    if (!getAddressBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, address_);
+    }
+    if (ipPort_ != 0) {
+      output.writeUInt32(3, ipPort_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (type_ != io.grpc.binarylog.v1.Address.Type.TYPE_UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(1, type_);
+    }
+    if (!getAddressBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, address_);
+    }
+    if (ipPort_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(3, 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.v1.Address)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.Address other = (io.grpc.binarylog.v1.Address) obj;
+
+    boolean result = true;
+    result = result && type_ == other.type_;
+    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) + TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + type_;
+    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.v1.Address parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.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.binarylog.v1.Address parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.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.binarylog.v1.Address parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Address parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.Address parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.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.binarylog.v1.Address parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.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.binarylog.v1.Address parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.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.binarylog.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 information
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1.Address}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.Address)
+      io.grpc.binarylog.v1.AddressOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Address_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Address_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.Address.class, io.grpc.binarylog.v1.Address.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.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();
+      type_ = 0;
+
+      address_ = "";
+
+      ipPort_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Address_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.Address getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.Address.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.Address build() {
+      io.grpc.binarylog.v1.Address result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.Address buildPartial() {
+      io.grpc.binarylog.v1.Address result = new io.grpc.binarylog.v1.Address(this);
+      result.type_ = type_;
+      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.v1.Address) {
+        return mergeFrom((io.grpc.binarylog.v1.Address)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.Address other) {
+      if (other == io.grpc.binarylog.v1.Address.getDefaultInstance()) return this;
+      if (other.type_ != 0) {
+        setTypeValue(other.getTypeValue());
+      }
+      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.v1.Address parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.Address) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int type_ = 0;
+    /**
+     * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+     */
+    public int getTypeValue() {
+      return type_;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+     */
+    public Builder setTypeValue(int value) {
+      type_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Address.Type getType() {
+      io.grpc.binarylog.v1.Address.Type result = io.grpc.binarylog.v1.Address.Type.valueOf(type_);
+      return result == null ? io.grpc.binarylog.v1.Address.Type.UNRECOGNIZED : result;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+     */
+    public Builder setType(io.grpc.binarylog.v1.Address.Type value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      type_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+     */
+    public Builder clearType() {
+      
+      type_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object address_ = "";
+    /**
+     * <code>string address = 2;</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 = 2;</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 = 2;</code>
+     */
+    public Builder setAddress(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      address_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string address = 2;</code>
+     */
+    public Builder clearAddress() {
+      
+      address_ = getDefaultInstance().getAddress();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string address = 2;</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 TYPE_IPV4 and TYPE_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 3;</code>
+     */
+    public int getIpPort() {
+      return ipPort_;
+    }
+    /**
+     * <pre>
+     * only for TYPE_IPV4 and TYPE_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 3;</code>
+     */
+    public Builder setIpPort(int value) {
+      
+      ipPort_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * only for TYPE_IPV4 and TYPE_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 3;</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.v1.Address)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.Address)
+  private static final io.grpc.binarylog.v1.Address DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.Address();
+  }
+
+  public static io.grpc.binarylog.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.binarylog.v1.Address getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/AddressOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/AddressOrBuilder.java
new file mode 100644
index 0000000..7bb9158
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/AddressOrBuilder.java
@@ -0,0 +1,37 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface AddressOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.Address)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+   */
+  int getTypeValue();
+  /**
+   * <code>.grpc.binarylog.v1.Address.Type type = 1;</code>
+   */
+  io.grpc.binarylog.v1.Address.Type getType();
+
+  /**
+   * <code>string address = 2;</code>
+   */
+  java.lang.String getAddress();
+  /**
+   * <code>string address = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getAddressBytes();
+
+  /**
+   * <pre>
+   * only for TYPE_IPV4 and TYPE_IPV6
+   * </pre>
+   *
+   * <code>uint32 ip_port = 3;</code>
+   */
+  int getIpPort();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/BinaryLogProto.java b/services/src/generated/main/java/io/grpc/binarylog/v1/BinaryLogProto.java
new file mode 100644
index 0000000..271d141
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/BinaryLogProto.java
@@ -0,0 +1,177 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+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_v1_GrpcLogEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_GrpcLogEntry_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_ClientHeader_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_ClientHeader_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_ServerHeader_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_ServerHeader_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_Trailer_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_Trailer_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_Message_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_Message_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_Metadata_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_Metadata_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_MetadataEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_MetadataEntry_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1_Address_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1_Address_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/binlog/v1/binarylog.proto\022\021grpc.b" +
+      "inarylog.v1\032\036google/protobuf/duration.pr" +
+      "oto\032\037google/protobuf/timestamp.proto\"\276\006\n" +
+      "\014GrpcLogEntry\022-\n\ttimestamp\030\001 \001(\0132\032.googl" +
+      "e.protobuf.Timestamp\022\017\n\007call_id\030\002 \001(\004\022\037\n" +
+      "\027sequence_id_within_call\030\003 \001(\004\0227\n\004type\030\004" +
+      " \001(\0162).grpc.binarylog.v1.GrpcLogEntry.Ev" +
+      "entType\0226\n\006logger\030\005 \001(\0162&.grpc.binarylog" +
+      ".v1.GrpcLogEntry.Logger\0228\n\rclient_header" +
+      "\030\006 \001(\0132\037.grpc.binarylog.v1.ClientHeaderH" +
+      "\000\0228\n\rserver_header\030\007 \001(\0132\037.grpc.binarylo" +
+      "g.v1.ServerHeaderH\000\022-\n\007message\030\010 \001(\0132\032.g" +
+      "rpc.binarylog.v1.MessageH\000\022-\n\007trailer\030\t " +
+      "\001(\0132\032.grpc.binarylog.v1.TrailerH\000\022\031\n\021pay" +
+      "load_truncated\030\n \001(\010\022(\n\004peer\030\013 \001(\0132\032.grp" +
+      "c.binarylog.v1.Address\"\365\001\n\tEventType\022\026\n\022" +
+      "EVENT_TYPE_UNKNOWN\020\000\022\034\n\030EVENT_TYPE_CLIEN" +
+      "T_HEADER\020\001\022\034\n\030EVENT_TYPE_SERVER_HEADER\020\002" +
+      "\022\035\n\031EVENT_TYPE_CLIENT_MESSAGE\020\003\022\035\n\031EVENT" +
+      "_TYPE_SERVER_MESSAGE\020\004\022 \n\034EVENT_TYPE_CLI" +
+      "ENT_HALF_CLOSE\020\005\022\035\n\031EVENT_TYPE_SERVER_TR" +
+      "AILER\020\006\022\025\n\021EVENT_TYPE_CANCEL\020\007\"B\n\006Logger" +
+      "\022\022\n\016LOGGER_UNKNOWN\020\000\022\021\n\rLOGGER_CLIENT\020\001\022" +
+      "\021\n\rLOGGER_SERVER\020\002B\t\n\007payload\"\221\001\n\014Client" +
+      "Header\022-\n\010metadata\030\001 \001(\0132\033.grpc.binarylo" +
+      "g.v1.Metadata\022\023\n\013method_name\030\002 \001(\t\022\021\n\tau" +
+      "thority\030\003 \001(\t\022*\n\007timeout\030\004 \001(\0132\031.google." +
+      "protobuf.Duration\"=\n\014ServerHeader\022-\n\010met" +
+      "adata\030\001 \001(\0132\033.grpc.binarylog.v1.Metadata" +
+      "\"}\n\007Trailer\022-\n\010metadata\030\001 \001(\0132\033.grpc.bin" +
+      "arylog.v1.Metadata\022\023\n\013status_code\030\002 \001(\r\022" +
+      "\026\n\016status_message\030\003 \001(\t\022\026\n\016status_detail" +
+      "s\030\004 \001(\014\"\'\n\007Message\022\016\n\006length\030\001 \001(\r\022\014\n\004da" +
+      "ta\030\002 \001(\014\";\n\010Metadata\022/\n\005entry\030\001 \003(\0132 .gr" +
+      "pc.binarylog.v1.MetadataEntry\"+\n\rMetadat" +
+      "aEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\014\"\241\001\n\007A" +
+      "ddress\022-\n\004type\030\001 \001(\0162\037.grpc.binarylog.v1" +
+      ".Address.Type\022\017\n\007address\030\002 \001(\t\022\017\n\007ip_por" +
+      "t\030\003 \001(\r\"E\n\004Type\022\020\n\014TYPE_UNKNOWN\020\000\022\r\n\tTYP" +
+      "E_IPV4\020\001\022\r\n\tTYPE_IPV6\020\002\022\r\n\tTYPE_UNIX\020\003B\\" +
+      "\n\024io.grpc.binarylog.v1B\016BinaryLogProtoP\001" +
+      "Z2google.golang.org/grpc/binarylog/grpc_" +
+      "binarylog_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.DurationProto.getDescriptor(),
+          com.google.protobuf.TimestampProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_binarylog_v1_GrpcLogEntry_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_binarylog_v1_GrpcLogEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_GrpcLogEntry_descriptor,
+        new java.lang.String[] { "Timestamp", "CallId", "SequenceIdWithinCall", "Type", "Logger", "ClientHeader", "ServerHeader", "Message", "Trailer", "PayloadTruncated", "Peer", "Payload", });
+    internal_static_grpc_binarylog_v1_ClientHeader_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_binarylog_v1_ClientHeader_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_ClientHeader_descriptor,
+        new java.lang.String[] { "Metadata", "MethodName", "Authority", "Timeout", });
+    internal_static_grpc_binarylog_v1_ServerHeader_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_binarylog_v1_ServerHeader_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_ServerHeader_descriptor,
+        new java.lang.String[] { "Metadata", });
+    internal_static_grpc_binarylog_v1_Trailer_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_binarylog_v1_Trailer_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_Trailer_descriptor,
+        new java.lang.String[] { "Metadata", "StatusCode", "StatusMessage", "StatusDetails", });
+    internal_static_grpc_binarylog_v1_Message_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_binarylog_v1_Message_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_Message_descriptor,
+        new java.lang.String[] { "Length", "Data", });
+    internal_static_grpc_binarylog_v1_Metadata_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_binarylog_v1_Metadata_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_Metadata_descriptor,
+        new java.lang.String[] { "Entry", });
+    internal_static_grpc_binarylog_v1_MetadataEntry_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_binarylog_v1_MetadataEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_MetadataEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    internal_static_grpc_binarylog_v1_Address_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_binarylog_v1_Address_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1_Address_descriptor,
+        new java.lang.String[] { "Type", "Address", "IpPort", });
+    com.google.protobuf.DurationProto.getDescriptor();
+    com.google.protobuf.TimestampProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeader.java b/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeader.java
new file mode 100644
index 0000000..02b4c3d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeader.java
@@ -0,0 +1,1199 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * Protobuf type {@code grpc.binarylog.v1.ClientHeader}
+ */
+public  final class ClientHeader extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.ClientHeader)
+    ClientHeaderOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ClientHeader.newBuilder() to construct.
+  private ClientHeader(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ClientHeader() {
+    methodName_ = "";
+    authority_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ClientHeader(
+      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.binarylog.v1.Metadata.Builder subBuilder = null;
+            if (metadata_ != null) {
+              subBuilder = metadata_.toBuilder();
+            }
+            metadata_ = input.readMessage(io.grpc.binarylog.v1.Metadata.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(metadata_);
+              metadata_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            methodName_ = s;
+            break;
+          }
+          case 26: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            authority_ = s;
+            break;
+          }
+          case 34: {
+            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;
+          }
+        }
+      }
+    } 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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ClientHeader_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ClientHeader_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.ClientHeader.class, io.grpc.binarylog.v1.ClientHeader.Builder.class);
+  }
+
+  public static final int METADATA_FIELD_NUMBER = 1;
+  private io.grpc.binarylog.v1.Metadata metadata_;
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public boolean hasMetadata() {
+    return metadata_ != null;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.Metadata getMetadata() {
+    return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+    return getMetadata();
+  }
+
+  public static final int METHOD_NAME_FIELD_NUMBER = 2;
+  private volatile java.lang.Object methodName_;
+  /**
+   * <pre>
+   * The name of the RPC method, which looks something like:
+   * /&lt;service&gt;/&lt;method&gt;
+   * Note the leading "/" character.
+   * </pre>
+   *
+   * <code>string method_name = 2;</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 name of the RPC method, which looks something like:
+   * /&lt;service&gt;/&lt;method&gt;
+   * Note the leading "/" character.
+   * </pre>
+   *
+   * <code>string method_name = 2;</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 AUTHORITY_FIELD_NUMBER = 3;
+  private volatile java.lang.Object authority_;
+  /**
+   * <pre>
+   * A single process may be used to run multiple virtual
+   * servers with different identities.
+   * The authority is the name of such a server identitiy.
+   * It is typically a portion of the URI in the form of
+   * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+   * </pre>
+   *
+   * <code>string authority = 3;</code>
+   */
+  public java.lang.String getAuthority() {
+    java.lang.Object ref = authority_;
+    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();
+      authority_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * A single process may be used to run multiple virtual
+   * servers with different identities.
+   * The authority is the name of such a server identitiy.
+   * It is typically a portion of the URI in the form of
+   * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+   * </pre>
+   *
+   * <code>string authority = 3;</code>
+   */
+  public com.google.protobuf.ByteString
+      getAuthorityBytes() {
+    java.lang.Object ref = authority_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      authority_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int TIMEOUT_FIELD_NUMBER = 4;
+  private com.google.protobuf.Duration timeout_;
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 4;</code>
+   */
+  public boolean hasTimeout() {
+    return timeout_ != null;
+  }
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 4;</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 = 4;</code>
+   */
+  public com.google.protobuf.DurationOrBuilder getTimeoutOrBuilder() {
+    return getTimeout();
+  }
+
+  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 (metadata_ != null) {
+      output.writeMessage(1, getMetadata());
+    }
+    if (!getMethodNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, methodName_);
+    }
+    if (!getAuthorityBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 3, authority_);
+    }
+    if (timeout_ != null) {
+      output.writeMessage(4, getTimeout());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (metadata_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getMetadata());
+    }
+    if (!getMethodNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, methodName_);
+    }
+    if (!getAuthorityBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, authority_);
+    }
+    if (timeout_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, getTimeout());
+    }
+    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.v1.ClientHeader)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.ClientHeader other = (io.grpc.binarylog.v1.ClientHeader) obj;
+
+    boolean result = true;
+    result = result && (hasMetadata() == other.hasMetadata());
+    if (hasMetadata()) {
+      result = result && getMetadata()
+          .equals(other.getMetadata());
+    }
+    result = result && getMethodName()
+        .equals(other.getMethodName());
+    result = result && getAuthority()
+        .equals(other.getAuthority());
+    result = result && (hasTimeout() == other.hasTimeout());
+    if (hasTimeout()) {
+      result = result && getTimeout()
+          .equals(other.getTimeout());
+    }
+    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 (hasMetadata()) {
+      hash = (37 * hash) + METADATA_FIELD_NUMBER;
+      hash = (53 * hash) + getMetadata().hashCode();
+    }
+    hash = (37 * hash) + METHOD_NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getMethodName().hashCode();
+    hash = (37 * hash) + AUTHORITY_FIELD_NUMBER;
+    hash = (53 * hash) + getAuthority().hashCode();
+    if (hasTimeout()) {
+      hash = (37 * hash) + TIMEOUT_FIELD_NUMBER;
+      hash = (53 * hash) + getTimeout().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1.ClientHeader parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader 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.v1.ClientHeader parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader 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.v1.ClientHeader parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader 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.v1.ClientHeader parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader 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.v1.ClientHeader parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ClientHeader 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.v1.ClientHeader 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.binarylog.v1.ClientHeader}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.ClientHeader)
+      io.grpc.binarylog.v1.ClientHeaderOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ClientHeader_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ClientHeader_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.ClientHeader.class, io.grpc.binarylog.v1.ClientHeader.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.ClientHeader.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 (metadataBuilder_ == null) {
+        metadata_ = null;
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+      methodName_ = "";
+
+      authority_ = "";
+
+      if (timeoutBuilder_ == null) {
+        timeout_ = null;
+      } else {
+        timeout_ = null;
+        timeoutBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ClientHeader_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.ClientHeader getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.ClientHeader build() {
+      io.grpc.binarylog.v1.ClientHeader result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.ClientHeader buildPartial() {
+      io.grpc.binarylog.v1.ClientHeader result = new io.grpc.binarylog.v1.ClientHeader(this);
+      if (metadataBuilder_ == null) {
+        result.metadata_ = metadata_;
+      } else {
+        result.metadata_ = metadataBuilder_.build();
+      }
+      result.methodName_ = methodName_;
+      result.authority_ = authority_;
+      if (timeoutBuilder_ == null) {
+        result.timeout_ = timeout_;
+      } else {
+        result.timeout_ = timeoutBuilder_.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.v1.ClientHeader) {
+        return mergeFrom((io.grpc.binarylog.v1.ClientHeader)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.ClientHeader other) {
+      if (other == io.grpc.binarylog.v1.ClientHeader.getDefaultInstance()) return this;
+      if (other.hasMetadata()) {
+        mergeMetadata(other.getMetadata());
+      }
+      if (!other.getMethodName().isEmpty()) {
+        methodName_ = other.methodName_;
+        onChanged();
+      }
+      if (!other.getAuthority().isEmpty()) {
+        authority_ = other.authority_;
+        onChanged();
+      }
+      if (other.hasTimeout()) {
+        mergeTimeout(other.getTimeout());
+      }
+      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.v1.ClientHeader parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.ClientHeader) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.binarylog.v1.Metadata metadata_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> metadataBuilder_;
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public boolean hasMetadata() {
+      return metadataBuilder_ != null || metadata_ != null;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata getMetadata() {
+      if (metadataBuilder_ == null) {
+        return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      } else {
+        return metadataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        metadata_ = value;
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(
+        io.grpc.binarylog.v1.Metadata.Builder builderForValue) {
+      if (metadataBuilder_ == null) {
+        metadata_ = builderForValue.build();
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder mergeMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (metadata_ != null) {
+          metadata_ =
+            io.grpc.binarylog.v1.Metadata.newBuilder(metadata_).mergeFrom(value).buildPartial();
+        } else {
+          metadata_ = value;
+        }
+        onChanged();
+      } else {
+        metadataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder clearMetadata() {
+      if (metadataBuilder_ == null) {
+        metadata_ = null;
+        onChanged();
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata.Builder getMetadataBuilder() {
+      
+      onChanged();
+      return getMetadataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+      if (metadataBuilder_ != null) {
+        return metadataBuilder_.getMessageOrBuilder();
+      } else {
+        return metadata_ == null ?
+            io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> 
+        getMetadataFieldBuilder() {
+      if (metadataBuilder_ == null) {
+        metadataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder>(
+                getMetadata(),
+                getParentForChildren(),
+                isClean());
+        metadata_ = null;
+      }
+      return metadataBuilder_;
+    }
+
+    private java.lang.Object methodName_ = "";
+    /**
+     * <pre>
+     * The name of the RPC method, which looks something like:
+     * /&lt;service&gt;/&lt;method&gt;
+     * Note the leading "/" character.
+     * </pre>
+     *
+     * <code>string method_name = 2;</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 name of the RPC method, which looks something like:
+     * /&lt;service&gt;/&lt;method&gt;
+     * Note the leading "/" character.
+     * </pre>
+     *
+     * <code>string method_name = 2;</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 name of the RPC method, which looks something like:
+     * /&lt;service&gt;/&lt;method&gt;
+     * Note the leading "/" character.
+     * </pre>
+     *
+     * <code>string method_name = 2;</code>
+     */
+    public Builder setMethodName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      methodName_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The name of the RPC method, which looks something like:
+     * /&lt;service&gt;/&lt;method&gt;
+     * Note the leading "/" character.
+     * </pre>
+     *
+     * <code>string method_name = 2;</code>
+     */
+    public Builder clearMethodName() {
+      
+      methodName_ = getDefaultInstance().getMethodName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The name of the RPC method, which looks something like:
+     * /&lt;service&gt;/&lt;method&gt;
+     * Note the leading "/" character.
+     * </pre>
+     *
+     * <code>string method_name = 2;</code>
+     */
+    public Builder setMethodNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      methodName_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object authority_ = "";
+    /**
+     * <pre>
+     * A single process may be used to run multiple virtual
+     * servers with different identities.
+     * The authority is the name of such a server identitiy.
+     * It is typically a portion of the URI in the form of
+     * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+     * </pre>
+     *
+     * <code>string authority = 3;</code>
+     */
+    public java.lang.String getAuthority() {
+      java.lang.Object ref = authority_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        authority_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * A single process may be used to run multiple virtual
+     * servers with different identities.
+     * The authority is the name of such a server identitiy.
+     * It is typically a portion of the URI in the form of
+     * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+     * </pre>
+     *
+     * <code>string authority = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getAuthorityBytes() {
+      java.lang.Object ref = authority_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        authority_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * A single process may be used to run multiple virtual
+     * servers with different identities.
+     * The authority is the name of such a server identitiy.
+     * It is typically a portion of the URI in the form of
+     * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+     * </pre>
+     *
+     * <code>string authority = 3;</code>
+     */
+    public Builder setAuthority(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      authority_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * A single process may be used to run multiple virtual
+     * servers with different identities.
+     * The authority is the name of such a server identitiy.
+     * It is typically a portion of the URI in the form of
+     * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+     * </pre>
+     *
+     * <code>string authority = 3;</code>
+     */
+    public Builder clearAuthority() {
+      
+      authority_ = getDefaultInstance().getAuthority();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * A single process may be used to run multiple virtual
+     * servers with different identities.
+     * The authority is the name of such a server identitiy.
+     * It is typically a portion of the URI in the form of
+     * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+     * </pre>
+     *
+     * <code>string authority = 3;</code>
+     */
+    public Builder setAuthorityBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      authority_ = value;
+      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 = 4;</code>
+     */
+    public boolean hasTimeout() {
+      return timeoutBuilder_ != null || timeout_ != null;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 4;</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 = 4;</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 = 4;</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 = 4;</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 = 4;</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 = 4;</code>
+     */
+    public com.google.protobuf.Duration.Builder getTimeoutBuilder() {
+      
+      onChanged();
+      return getTimeoutFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 4;</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 = 4;</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_;
+    }
+    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.v1.ClientHeader)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.ClientHeader)
+  private static final io.grpc.binarylog.v1.ClientHeader DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.ClientHeader();
+  }
+
+  public static io.grpc.binarylog.v1.ClientHeader getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ClientHeader>
+      PARSER = new com.google.protobuf.AbstractParser<ClientHeader>() {
+    public ClientHeader parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ClientHeader(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ClientHeader> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ClientHeader> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1.ClientHeader getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeaderOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeaderOrBuilder.java
new file mode 100644
index 0000000..3dab835
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/ClientHeaderOrBuilder.java
@@ -0,0 +1,107 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface ClientHeaderOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.ClientHeader)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  boolean hasMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.Metadata getMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder();
+
+  /**
+   * <pre>
+   * The name of the RPC method, which looks something like:
+   * /&lt;service&gt;/&lt;method&gt;
+   * Note the leading "/" character.
+   * </pre>
+   *
+   * <code>string method_name = 2;</code>
+   */
+  java.lang.String getMethodName();
+  /**
+   * <pre>
+   * The name of the RPC method, which looks something like:
+   * /&lt;service&gt;/&lt;method&gt;
+   * Note the leading "/" character.
+   * </pre>
+   *
+   * <code>string method_name = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getMethodNameBytes();
+
+  /**
+   * <pre>
+   * A single process may be used to run multiple virtual
+   * servers with different identities.
+   * The authority is the name of such a server identitiy.
+   * It is typically a portion of the URI in the form of
+   * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+   * </pre>
+   *
+   * <code>string authority = 3;</code>
+   */
+  java.lang.String getAuthority();
+  /**
+   * <pre>
+   * A single process may be used to run multiple virtual
+   * servers with different identities.
+   * The authority is the name of such a server identitiy.
+   * It is typically a portion of the URI in the form of
+   * &lt;host&gt; or &lt;host&gt;:&lt;port&gt; .
+   * </pre>
+   *
+   * <code>string authority = 3;</code>
+   */
+  com.google.protobuf.ByteString
+      getAuthorityBytes();
+
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 4;</code>
+   */
+  boolean hasTimeout();
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 4;</code>
+   */
+  com.google.protobuf.Duration getTimeout();
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 4;</code>
+   */
+  com.google.protobuf.DurationOrBuilder getTimeoutOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntry.java b/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntry.java
new file mode 100644
index 0000000..4f885a2
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntry.java
@@ -0,0 +1,2629 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * <pre>
+ * Log entry we store in binary logs
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1.GrpcLogEntry}
+ */
+public  final class GrpcLogEntry extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.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() {
+    callId_ = 0L;
+    sequenceIdWithinCall_ = 0L;
+    type_ = 0;
+    logger_ = 0;
+    payloadTruncated_ = false;
+  }
+
+  @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 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: {
+
+            callId_ = input.readUInt64();
+            break;
+          }
+          case 24: {
+
+            sequenceIdWithinCall_ = input.readUInt64();
+            break;
+          }
+          case 32: {
+            int rawValue = input.readEnum();
+
+            type_ = rawValue;
+            break;
+          }
+          case 40: {
+            int rawValue = input.readEnum();
+
+            logger_ = rawValue;
+            break;
+          }
+          case 50: {
+            io.grpc.binarylog.v1.ClientHeader.Builder subBuilder = null;
+            if (payloadCase_ == 6) {
+              subBuilder = ((io.grpc.binarylog.v1.ClientHeader) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1.ClientHeader.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1.ClientHeader) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 6;
+            break;
+          }
+          case 58: {
+            io.grpc.binarylog.v1.ServerHeader.Builder subBuilder = null;
+            if (payloadCase_ == 7) {
+              subBuilder = ((io.grpc.binarylog.v1.ServerHeader) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1.ServerHeader.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1.ServerHeader) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 7;
+            break;
+          }
+          case 66: {
+            io.grpc.binarylog.v1.Message.Builder subBuilder = null;
+            if (payloadCase_ == 8) {
+              subBuilder = ((io.grpc.binarylog.v1.Message) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1.Message.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1.Message) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 8;
+            break;
+          }
+          case 74: {
+            io.grpc.binarylog.v1.Trailer.Builder subBuilder = null;
+            if (payloadCase_ == 9) {
+              subBuilder = ((io.grpc.binarylog.v1.Trailer) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1.Trailer.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1.Trailer) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 9;
+            break;
+          }
+          case 80: {
+
+            payloadTruncated_ = input.readBool();
+            break;
+          }
+          case 90: {
+            io.grpc.binarylog.v1.Address.Builder subBuilder = null;
+            if (peer_ != null) {
+              subBuilder = peer_.toBuilder();
+            }
+            peer_ = input.readMessage(io.grpc.binarylog.v1.Address.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(peer_);
+              peer_ = 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.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_GrpcLogEntry_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_GrpcLogEntry_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.GrpcLogEntry.class, io.grpc.binarylog.v1.GrpcLogEntry.Builder.class);
+  }
+
+  /**
+   * <pre>
+   * Enumerates the type of event
+   * Note the terminology is different from the RPC semantics
+   * definition, but the same meaning is expressed here.
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.binarylog.v1.GrpcLogEntry.EventType}
+   */
+  public enum EventType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>EVENT_TYPE_UNKNOWN = 0;</code>
+     */
+    EVENT_TYPE_UNKNOWN(0),
+    /**
+     * <pre>
+     * Header sent from client to server
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_HEADER = 1;</code>
+     */
+    EVENT_TYPE_CLIENT_HEADER(1),
+    /**
+     * <pre>
+     * Header sent from server to client
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_HEADER = 2;</code>
+     */
+    EVENT_TYPE_SERVER_HEADER(2),
+    /**
+     * <pre>
+     * Message sent from client to server
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_MESSAGE = 3;</code>
+     */
+    EVENT_TYPE_CLIENT_MESSAGE(3),
+    /**
+     * <pre>
+     * Message sent from server to client
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_MESSAGE = 4;</code>
+     */
+    EVENT_TYPE_SERVER_MESSAGE(4),
+    /**
+     * <pre>
+     * A signal that client is done sending
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_HALF_CLOSE = 5;</code>
+     */
+    EVENT_TYPE_CLIENT_HALF_CLOSE(5),
+    /**
+     * <pre>
+     * Trailer indicates the end of the RPC.
+     * On client side, this event means a trailer was either received
+     * from the network or the gRPC library locally generated a status
+     * to inform the application about a failure.
+     * On server side, this event means the server application requested
+     * to send a trailer. Note: EVENT_TYPE_CANCEL may still arrive after
+     * this due to races on server side.
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_TRAILER = 6;</code>
+     */
+    EVENT_TYPE_SERVER_TRAILER(6),
+    /**
+     * <pre>
+     * A signal that the RPC is cancelled. On client side, this
+     * indicates the client application requests a cancellation.
+     * On server side, this indicates that cancellation was detected.
+     * Note: This marks the end of the RPC. Events may arrive after
+     * this due to races. For example, on client side a trailer
+     * may arrive even though the application requested to cancel the RPC.
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CANCEL = 7;</code>
+     */
+    EVENT_TYPE_CANCEL(7),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>EVENT_TYPE_UNKNOWN = 0;</code>
+     */
+    public static final int EVENT_TYPE_UNKNOWN_VALUE = 0;
+    /**
+     * <pre>
+     * Header sent from client to server
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_HEADER = 1;</code>
+     */
+    public static final int EVENT_TYPE_CLIENT_HEADER_VALUE = 1;
+    /**
+     * <pre>
+     * Header sent from server to client
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_HEADER = 2;</code>
+     */
+    public static final int EVENT_TYPE_SERVER_HEADER_VALUE = 2;
+    /**
+     * <pre>
+     * Message sent from client to server
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_MESSAGE = 3;</code>
+     */
+    public static final int EVENT_TYPE_CLIENT_MESSAGE_VALUE = 3;
+    /**
+     * <pre>
+     * Message sent from server to client
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_MESSAGE = 4;</code>
+     */
+    public static final int EVENT_TYPE_SERVER_MESSAGE_VALUE = 4;
+    /**
+     * <pre>
+     * A signal that client is done sending
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CLIENT_HALF_CLOSE = 5;</code>
+     */
+    public static final int EVENT_TYPE_CLIENT_HALF_CLOSE_VALUE = 5;
+    /**
+     * <pre>
+     * Trailer indicates the end of the RPC.
+     * On client side, this event means a trailer was either received
+     * from the network or the gRPC library locally generated a status
+     * to inform the application about a failure.
+     * On server side, this event means the server application requested
+     * to send a trailer. Note: EVENT_TYPE_CANCEL may still arrive after
+     * this due to races on server side.
+     * </pre>
+     *
+     * <code>EVENT_TYPE_SERVER_TRAILER = 6;</code>
+     */
+    public static final int EVENT_TYPE_SERVER_TRAILER_VALUE = 6;
+    /**
+     * <pre>
+     * A signal that the RPC is cancelled. On client side, this
+     * indicates the client application requests a cancellation.
+     * On server side, this indicates that cancellation was detected.
+     * Note: This marks the end of the RPC. Events may arrive after
+     * this due to races. For example, on client side a trailer
+     * may arrive even though the application requested to cancel the RPC.
+     * </pre>
+     *
+     * <code>EVENT_TYPE_CANCEL = 7;</code>
+     */
+    public static final int EVENT_TYPE_CANCEL_VALUE = 7;
+
+
+    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 EventType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static EventType forNumber(int value) {
+      switch (value) {
+        case 0: return EVENT_TYPE_UNKNOWN;
+        case 1: return EVENT_TYPE_CLIENT_HEADER;
+        case 2: return EVENT_TYPE_SERVER_HEADER;
+        case 3: return EVENT_TYPE_CLIENT_MESSAGE;
+        case 4: return EVENT_TYPE_SERVER_MESSAGE;
+        case 5: return EVENT_TYPE_CLIENT_HALF_CLOSE;
+        case 6: return EVENT_TYPE_SERVER_TRAILER;
+        case 7: return EVENT_TYPE_CANCEL;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<EventType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        EventType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<EventType>() {
+            public EventType findValueByNumber(int number) {
+              return EventType.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.v1.GrpcLogEntry.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final EventType[] VALUES = values();
+
+    public static EventType 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 EventType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.binarylog.v1.GrpcLogEntry.EventType)
+  }
+
+  /**
+   * <pre>
+   * Enumerates the entity that generates the log entry
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.binarylog.v1.GrpcLogEntry.Logger}
+   */
+  public enum Logger
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>LOGGER_UNKNOWN = 0;</code>
+     */
+    LOGGER_UNKNOWN(0),
+    /**
+     * <code>LOGGER_CLIENT = 1;</code>
+     */
+    LOGGER_CLIENT(1),
+    /**
+     * <code>LOGGER_SERVER = 2;</code>
+     */
+    LOGGER_SERVER(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>LOGGER_UNKNOWN = 0;</code>
+     */
+    public static final int LOGGER_UNKNOWN_VALUE = 0;
+    /**
+     * <code>LOGGER_CLIENT = 1;</code>
+     */
+    public static final int LOGGER_CLIENT_VALUE = 1;
+    /**
+     * <code>LOGGER_SERVER = 2;</code>
+     */
+    public static final int LOGGER_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 LOGGER_UNKNOWN;
+        case 1: return LOGGER_CLIENT;
+        case 2: return LOGGER_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.v1.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.v1.GrpcLogEntry.Logger)
+  }
+
+  private int payloadCase_ = 0;
+  private java.lang.Object payload_;
+  public enum PayloadCase
+      implements com.google.protobuf.Internal.EnumLite {
+    CLIENT_HEADER(6),
+    SERVER_HEADER(7),
+    MESSAGE(8),
+    TRAILER(9),
+    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 6: return CLIENT_HEADER;
+        case 7: return SERVER_HEADER;
+        case 8: return MESSAGE;
+        case 9: return TRAILER;
+        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 TIMESTAMP_FIELD_NUMBER = 1;
+  private com.google.protobuf.Timestamp timestamp_;
+  /**
+   * <pre>
+   * The timestamp of the binary log message
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  public boolean hasTimestamp() {
+    return timestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The timestamp of the binary log message
+   * </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 the binary log message
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
+    return getTimestamp();
+  }
+
+  public static final int CALL_ID_FIELD_NUMBER = 2;
+  private long callId_;
+  /**
+   * <pre>
+   * Uniquely identifies a call. The value must not be 0 in order to disambiguate
+   * from an unset value.
+   * Each call may have several log entries, they will all have the same call_id.
+   * Nothing is guaranteed about their value other than they are unique across
+   * different RPCs in the same gRPC process.
+   * </pre>
+   *
+   * <code>uint64 call_id = 2;</code>
+   */
+  public long getCallId() {
+    return callId_;
+  }
+
+  public static final int SEQUENCE_ID_WITHIN_CALL_FIELD_NUMBER = 3;
+  private long 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>uint64 sequence_id_within_call = 3;</code>
+   */
+  public long getSequenceIdWithinCall() {
+    return sequenceIdWithinCall_;
+  }
+
+  public static final int TYPE_FIELD_NUMBER = 4;
+  private int type_;
+  /**
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+   */
+  public int getTypeValue() {
+    return type_;
+  }
+  /**
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+   */
+  public io.grpc.binarylog.v1.GrpcLogEntry.EventType getType() {
+    io.grpc.binarylog.v1.GrpcLogEntry.EventType result = io.grpc.binarylog.v1.GrpcLogEntry.EventType.valueOf(type_);
+    return result == null ? io.grpc.binarylog.v1.GrpcLogEntry.EventType.UNRECOGNIZED : result;
+  }
+
+  public static final int LOGGER_FIELD_NUMBER = 5;
+  private int logger_;
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+   */
+  public int getLoggerValue() {
+    return logger_;
+  }
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+   */
+  public io.grpc.binarylog.v1.GrpcLogEntry.Logger getLogger() {
+    io.grpc.binarylog.v1.GrpcLogEntry.Logger result = io.grpc.binarylog.v1.GrpcLogEntry.Logger.valueOf(logger_);
+    return result == null ? io.grpc.binarylog.v1.GrpcLogEntry.Logger.UNRECOGNIZED : result;
+  }
+
+  public static final int CLIENT_HEADER_FIELD_NUMBER = 6;
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  public boolean hasClientHeader() {
+    return payloadCase_ == 6;
+  }
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  public io.grpc.binarylog.v1.ClientHeader getClientHeader() {
+    if (payloadCase_ == 6) {
+       return (io.grpc.binarylog.v1.ClientHeader) payload_;
+    }
+    return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  public io.grpc.binarylog.v1.ClientHeaderOrBuilder getClientHeaderOrBuilder() {
+    if (payloadCase_ == 6) {
+       return (io.grpc.binarylog.v1.ClientHeader) payload_;
+    }
+    return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+  }
+
+  public static final int SERVER_HEADER_FIELD_NUMBER = 7;
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  public boolean hasServerHeader() {
+    return payloadCase_ == 7;
+  }
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  public io.grpc.binarylog.v1.ServerHeader getServerHeader() {
+    if (payloadCase_ == 7) {
+       return (io.grpc.binarylog.v1.ServerHeader) payload_;
+    }
+    return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  public io.grpc.binarylog.v1.ServerHeaderOrBuilder getServerHeaderOrBuilder() {
+    if (payloadCase_ == 7) {
+       return (io.grpc.binarylog.v1.ServerHeader) payload_;
+    }
+    return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+  }
+
+  public static final int MESSAGE_FIELD_NUMBER = 8;
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  public boolean hasMessage() {
+    return payloadCase_ == 8;
+  }
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  public io.grpc.binarylog.v1.Message getMessage() {
+    if (payloadCase_ == 8) {
+       return (io.grpc.binarylog.v1.Message) payload_;
+    }
+    return io.grpc.binarylog.v1.Message.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  public io.grpc.binarylog.v1.MessageOrBuilder getMessageOrBuilder() {
+    if (payloadCase_ == 8) {
+       return (io.grpc.binarylog.v1.Message) payload_;
+    }
+    return io.grpc.binarylog.v1.Message.getDefaultInstance();
+  }
+
+  public static final int TRAILER_FIELD_NUMBER = 9;
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  public boolean hasTrailer() {
+    return payloadCase_ == 9;
+  }
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  public io.grpc.binarylog.v1.Trailer getTrailer() {
+    if (payloadCase_ == 9) {
+       return (io.grpc.binarylog.v1.Trailer) payload_;
+    }
+    return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  public io.grpc.binarylog.v1.TrailerOrBuilder getTrailerOrBuilder() {
+    if (payloadCase_ == 9) {
+       return (io.grpc.binarylog.v1.Trailer) payload_;
+    }
+    return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+  }
+
+  public static final int PAYLOAD_TRUNCATED_FIELD_NUMBER = 10;
+  private boolean payloadTruncated_;
+  /**
+   * <pre>
+   * true if payload does not represent the full message or metadata.
+   * </pre>
+   *
+   * <code>bool payload_truncated = 10;</code>
+   */
+  public boolean getPayloadTruncated() {
+    return payloadTruncated_;
+  }
+
+  public static final int PEER_FIELD_NUMBER = 11;
+  private io.grpc.binarylog.v1.Address peer_;
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  public boolean hasPeer() {
+    return peer_ != null;
+  }
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  public io.grpc.binarylog.v1.Address getPeer() {
+    return peer_ == null ? io.grpc.binarylog.v1.Address.getDefaultInstance() : peer_;
+  }
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  public io.grpc.binarylog.v1.AddressOrBuilder getPeerOrBuilder() {
+    return getPeer();
+  }
+
+  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 (callId_ != 0L) {
+      output.writeUInt64(2, callId_);
+    }
+    if (sequenceIdWithinCall_ != 0L) {
+      output.writeUInt64(3, sequenceIdWithinCall_);
+    }
+    if (type_ != io.grpc.binarylog.v1.GrpcLogEntry.EventType.EVENT_TYPE_UNKNOWN.getNumber()) {
+      output.writeEnum(4, type_);
+    }
+    if (logger_ != io.grpc.binarylog.v1.GrpcLogEntry.Logger.LOGGER_UNKNOWN.getNumber()) {
+      output.writeEnum(5, logger_);
+    }
+    if (payloadCase_ == 6) {
+      output.writeMessage(6, (io.grpc.binarylog.v1.ClientHeader) payload_);
+    }
+    if (payloadCase_ == 7) {
+      output.writeMessage(7, (io.grpc.binarylog.v1.ServerHeader) payload_);
+    }
+    if (payloadCase_ == 8) {
+      output.writeMessage(8, (io.grpc.binarylog.v1.Message) payload_);
+    }
+    if (payloadCase_ == 9) {
+      output.writeMessage(9, (io.grpc.binarylog.v1.Trailer) payload_);
+    }
+    if (payloadTruncated_ != false) {
+      output.writeBool(10, payloadTruncated_);
+    }
+    if (peer_ != null) {
+      output.writeMessage(11, getPeer());
+    }
+    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 (callId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt64Size(2, callId_);
+    }
+    if (sequenceIdWithinCall_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt64Size(3, sequenceIdWithinCall_);
+    }
+    if (type_ != io.grpc.binarylog.v1.GrpcLogEntry.EventType.EVENT_TYPE_UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(4, type_);
+    }
+    if (logger_ != io.grpc.binarylog.v1.GrpcLogEntry.Logger.LOGGER_UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(5, logger_);
+    }
+    if (payloadCase_ == 6) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(6, (io.grpc.binarylog.v1.ClientHeader) payload_);
+    }
+    if (payloadCase_ == 7) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(7, (io.grpc.binarylog.v1.ServerHeader) payload_);
+    }
+    if (payloadCase_ == 8) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(8, (io.grpc.binarylog.v1.Message) payload_);
+    }
+    if (payloadCase_ == 9) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(9, (io.grpc.binarylog.v1.Trailer) payload_);
+    }
+    if (payloadTruncated_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(10, payloadTruncated_);
+    }
+    if (peer_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(11, getPeer());
+    }
+    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.v1.GrpcLogEntry)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.GrpcLogEntry other = (io.grpc.binarylog.v1.GrpcLogEntry) obj;
+
+    boolean result = true;
+    result = result && (hasTimestamp() == other.hasTimestamp());
+    if (hasTimestamp()) {
+      result = result && getTimestamp()
+          .equals(other.getTimestamp());
+    }
+    result = result && (getCallId()
+        == other.getCallId());
+    result = result && (getSequenceIdWithinCall()
+        == other.getSequenceIdWithinCall());
+    result = result && type_ == other.type_;
+    result = result && logger_ == other.logger_;
+    result = result && (getPayloadTruncated()
+        == other.getPayloadTruncated());
+    result = result && (hasPeer() == other.hasPeer());
+    if (hasPeer()) {
+      result = result && getPeer()
+          .equals(other.getPeer());
+    }
+    result = result && getPayloadCase().equals(
+        other.getPayloadCase());
+    if (!result) return false;
+    switch (payloadCase_) {
+      case 6:
+        result = result && getClientHeader()
+            .equals(other.getClientHeader());
+        break;
+      case 7:
+        result = result && getServerHeader()
+            .equals(other.getServerHeader());
+        break;
+      case 8:
+        result = result && getMessage()
+            .equals(other.getMessage());
+        break;
+      case 9:
+        result = result && getTrailer()
+            .equals(other.getTrailer());
+        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();
+    if (hasTimestamp()) {
+      hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getTimestamp().hashCode();
+    }
+    hash = (37 * hash) + CALL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallId());
+    hash = (37 * hash) + SEQUENCE_ID_WITHIN_CALL_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getSequenceIdWithinCall());
+    hash = (37 * hash) + TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + type_;
+    hash = (37 * hash) + LOGGER_FIELD_NUMBER;
+    hash = (53 * hash) + logger_;
+    hash = (37 * hash) + PAYLOAD_TRUNCATED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getPayloadTruncated());
+    if (hasPeer()) {
+      hash = (37 * hash) + PEER_FIELD_NUMBER;
+      hash = (53 * hash) + getPeer().hashCode();
+    }
+    switch (payloadCase_) {
+      case 6:
+        hash = (37 * hash) + CLIENT_HEADER_FIELD_NUMBER;
+        hash = (53 * hash) + getClientHeader().hashCode();
+        break;
+      case 7:
+        hash = (37 * hash) + SERVER_HEADER_FIELD_NUMBER;
+        hash = (53 * hash) + getServerHeader().hashCode();
+        break;
+      case 8:
+        hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+        hash = (53 * hash) + getMessage().hashCode();
+        break;
+      case 9:
+        hash = (37 * hash) + TRAILER_FIELD_NUMBER;
+        hash = (53 * hash) + getTrailer().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1.GrpcLogEntry parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.GrpcLogEntry parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.GrpcLogEntry parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.GrpcLogEntry parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.GrpcLogEntry parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.GrpcLogEntry parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.GrpcLogEntry parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.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.v1.GrpcLogEntry}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.GrpcLogEntry)
+      io.grpc.binarylog.v1.GrpcLogEntryOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_GrpcLogEntry_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_GrpcLogEntry_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.GrpcLogEntry.class, io.grpc.binarylog.v1.GrpcLogEntry.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.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();
+      if (timestampBuilder_ == null) {
+        timestamp_ = null;
+      } else {
+        timestamp_ = null;
+        timestampBuilder_ = null;
+      }
+      callId_ = 0L;
+
+      sequenceIdWithinCall_ = 0L;
+
+      type_ = 0;
+
+      logger_ = 0;
+
+      payloadTruncated_ = false;
+
+      if (peerBuilder_ == null) {
+        peer_ = null;
+      } else {
+        peer_ = null;
+        peerBuilder_ = null;
+      }
+      payloadCase_ = 0;
+      payload_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_GrpcLogEntry_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.GrpcLogEntry getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.GrpcLogEntry.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.GrpcLogEntry build() {
+      io.grpc.binarylog.v1.GrpcLogEntry result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.GrpcLogEntry buildPartial() {
+      io.grpc.binarylog.v1.GrpcLogEntry result = new io.grpc.binarylog.v1.GrpcLogEntry(this);
+      if (timestampBuilder_ == null) {
+        result.timestamp_ = timestamp_;
+      } else {
+        result.timestamp_ = timestampBuilder_.build();
+      }
+      result.callId_ = callId_;
+      result.sequenceIdWithinCall_ = sequenceIdWithinCall_;
+      result.type_ = type_;
+      result.logger_ = logger_;
+      if (payloadCase_ == 6) {
+        if (clientHeaderBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = clientHeaderBuilder_.build();
+        }
+      }
+      if (payloadCase_ == 7) {
+        if (serverHeaderBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = serverHeaderBuilder_.build();
+        }
+      }
+      if (payloadCase_ == 8) {
+        if (messageBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = messageBuilder_.build();
+        }
+      }
+      if (payloadCase_ == 9) {
+        if (trailerBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = trailerBuilder_.build();
+        }
+      }
+      result.payloadTruncated_ = payloadTruncated_;
+      if (peerBuilder_ == null) {
+        result.peer_ = peer_;
+      } else {
+        result.peer_ = peerBuilder_.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.binarylog.v1.GrpcLogEntry) {
+        return mergeFrom((io.grpc.binarylog.v1.GrpcLogEntry)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.GrpcLogEntry other) {
+      if (other == io.grpc.binarylog.v1.GrpcLogEntry.getDefaultInstance()) return this;
+      if (other.hasTimestamp()) {
+        mergeTimestamp(other.getTimestamp());
+      }
+      if (other.getCallId() != 0L) {
+        setCallId(other.getCallId());
+      }
+      if (other.getSequenceIdWithinCall() != 0L) {
+        setSequenceIdWithinCall(other.getSequenceIdWithinCall());
+      }
+      if (other.type_ != 0) {
+        setTypeValue(other.getTypeValue());
+      }
+      if (other.logger_ != 0) {
+        setLoggerValue(other.getLoggerValue());
+      }
+      if (other.getPayloadTruncated() != false) {
+        setPayloadTruncated(other.getPayloadTruncated());
+      }
+      if (other.hasPeer()) {
+        mergePeer(other.getPeer());
+      }
+      switch (other.getPayloadCase()) {
+        case CLIENT_HEADER: {
+          mergeClientHeader(other.getClientHeader());
+          break;
+        }
+        case SERVER_HEADER: {
+          mergeServerHeader(other.getServerHeader());
+          break;
+        }
+        case MESSAGE: {
+          mergeMessage(other.getMessage());
+          break;
+        }
+        case TRAILER: {
+          mergeTrailer(other.getTrailer());
+          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.v1.GrpcLogEntry parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.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 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 the binary log message
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public boolean hasTimestamp() {
+      return timestampBuilder_ != null || timestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The timestamp of the binary log message
+     * </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 the binary log message
+     * </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 the binary log message
+     * </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 the binary log message
+     * </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 the binary log message
+     * </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 the binary log message
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getTimestampBuilder() {
+      
+      onChanged();
+      return getTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The timestamp of the binary log message
+     * </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 the binary log message
+     * </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 callId_ ;
+    /**
+     * <pre>
+     * Uniquely identifies a call. The value must not be 0 in order to disambiguate
+     * from an unset value.
+     * Each call may have several log entries, they will all have the same call_id.
+     * Nothing is guaranteed about their value other than they are unique across
+     * different RPCs in the same gRPC process.
+     * </pre>
+     *
+     * <code>uint64 call_id = 2;</code>
+     */
+    public long getCallId() {
+      return callId_;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. The value must not be 0 in order to disambiguate
+     * from an unset value.
+     * Each call may have several log entries, they will all have the same call_id.
+     * Nothing is guaranteed about their value other than they are unique across
+     * different RPCs in the same gRPC process.
+     * </pre>
+     *
+     * <code>uint64 call_id = 2;</code>
+     */
+    public Builder setCallId(long value) {
+      
+      callId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. The value must not be 0 in order to disambiguate
+     * from an unset value.
+     * Each call may have several log entries, they will all have the same call_id.
+     * Nothing is guaranteed about their value other than they are unique across
+     * different RPCs in the same gRPC process.
+     * </pre>
+     *
+     * <code>uint64 call_id = 2;</code>
+     */
+    public Builder clearCallId() {
+      
+      callId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long 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>uint64 sequence_id_within_call = 3;</code>
+     */
+    public long 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>uint64 sequence_id_within_call = 3;</code>
+     */
+    public Builder setSequenceIdWithinCall(long 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>uint64 sequence_id_within_call = 3;</code>
+     */
+    public Builder clearSequenceIdWithinCall() {
+      
+      sequenceIdWithinCall_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private int type_ = 0;
+    /**
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+     */
+    public int getTypeValue() {
+      return type_;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+     */
+    public Builder setTypeValue(int value) {
+      type_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+     */
+    public io.grpc.binarylog.v1.GrpcLogEntry.EventType getType() {
+      io.grpc.binarylog.v1.GrpcLogEntry.EventType result = io.grpc.binarylog.v1.GrpcLogEntry.EventType.valueOf(type_);
+      return result == null ? io.grpc.binarylog.v1.GrpcLogEntry.EventType.UNRECOGNIZED : result;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+     */
+    public Builder setType(io.grpc.binarylog.v1.GrpcLogEntry.EventType value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      type_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+     */
+    public Builder clearType() {
+      
+      type_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int logger_ = 0;
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+     */
+    public int getLoggerValue() {
+      return logger_;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+     */
+    public Builder setLoggerValue(int value) {
+      logger_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+     */
+    public io.grpc.binarylog.v1.GrpcLogEntry.Logger getLogger() {
+      io.grpc.binarylog.v1.GrpcLogEntry.Logger result = io.grpc.binarylog.v1.GrpcLogEntry.Logger.valueOf(logger_);
+      return result == null ? io.grpc.binarylog.v1.GrpcLogEntry.Logger.UNRECOGNIZED : result;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+     */
+    public Builder setLogger(io.grpc.binarylog.v1.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.v1.GrpcLogEntry.Logger logger = 5;</code>
+     */
+    public Builder clearLogger() {
+      
+      logger_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.ClientHeader, io.grpc.binarylog.v1.ClientHeader.Builder, io.grpc.binarylog.v1.ClientHeaderOrBuilder> clientHeaderBuilder_;
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public boolean hasClientHeader() {
+      return payloadCase_ == 6;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public io.grpc.binarylog.v1.ClientHeader getClientHeader() {
+      if (clientHeaderBuilder_ == null) {
+        if (payloadCase_ == 6) {
+          return (io.grpc.binarylog.v1.ClientHeader) payload_;
+        }
+        return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 6) {
+          return clientHeaderBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public Builder setClientHeader(io.grpc.binarylog.v1.ClientHeader value) {
+      if (clientHeaderBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        clientHeaderBuilder_.setMessage(value);
+      }
+      payloadCase_ = 6;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public Builder setClientHeader(
+        io.grpc.binarylog.v1.ClientHeader.Builder builderForValue) {
+      if (clientHeaderBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        clientHeaderBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 6;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public Builder mergeClientHeader(io.grpc.binarylog.v1.ClientHeader value) {
+      if (clientHeaderBuilder_ == null) {
+        if (payloadCase_ == 6 &&
+            payload_ != io.grpc.binarylog.v1.ClientHeader.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1.ClientHeader.newBuilder((io.grpc.binarylog.v1.ClientHeader) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 6) {
+          clientHeaderBuilder_.mergeFrom(value);
+        }
+        clientHeaderBuilder_.setMessage(value);
+      }
+      payloadCase_ = 6;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public Builder clearClientHeader() {
+      if (clientHeaderBuilder_ == null) {
+        if (payloadCase_ == 6) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 6) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        clientHeaderBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public io.grpc.binarylog.v1.ClientHeader.Builder getClientHeaderBuilder() {
+      return getClientHeaderFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    public io.grpc.binarylog.v1.ClientHeaderOrBuilder getClientHeaderOrBuilder() {
+      if ((payloadCase_ == 6) && (clientHeaderBuilder_ != null)) {
+        return clientHeaderBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 6) {
+          return (io.grpc.binarylog.v1.ClientHeader) payload_;
+        }
+        return io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.ClientHeader, io.grpc.binarylog.v1.ClientHeader.Builder, io.grpc.binarylog.v1.ClientHeaderOrBuilder> 
+        getClientHeaderFieldBuilder() {
+      if (clientHeaderBuilder_ == null) {
+        if (!(payloadCase_ == 6)) {
+          payload_ = io.grpc.binarylog.v1.ClientHeader.getDefaultInstance();
+        }
+        clientHeaderBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.ClientHeader, io.grpc.binarylog.v1.ClientHeader.Builder, io.grpc.binarylog.v1.ClientHeaderOrBuilder>(
+                (io.grpc.binarylog.v1.ClientHeader) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 6;
+      onChanged();;
+      return clientHeaderBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.ServerHeader, io.grpc.binarylog.v1.ServerHeader.Builder, io.grpc.binarylog.v1.ServerHeaderOrBuilder> serverHeaderBuilder_;
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public boolean hasServerHeader() {
+      return payloadCase_ == 7;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public io.grpc.binarylog.v1.ServerHeader getServerHeader() {
+      if (serverHeaderBuilder_ == null) {
+        if (payloadCase_ == 7) {
+          return (io.grpc.binarylog.v1.ServerHeader) payload_;
+        }
+        return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 7) {
+          return serverHeaderBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public Builder setServerHeader(io.grpc.binarylog.v1.ServerHeader value) {
+      if (serverHeaderBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        serverHeaderBuilder_.setMessage(value);
+      }
+      payloadCase_ = 7;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public Builder setServerHeader(
+        io.grpc.binarylog.v1.ServerHeader.Builder builderForValue) {
+      if (serverHeaderBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        serverHeaderBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 7;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public Builder mergeServerHeader(io.grpc.binarylog.v1.ServerHeader value) {
+      if (serverHeaderBuilder_ == null) {
+        if (payloadCase_ == 7 &&
+            payload_ != io.grpc.binarylog.v1.ServerHeader.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1.ServerHeader.newBuilder((io.grpc.binarylog.v1.ServerHeader) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 7) {
+          serverHeaderBuilder_.mergeFrom(value);
+        }
+        serverHeaderBuilder_.setMessage(value);
+      }
+      payloadCase_ = 7;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public Builder clearServerHeader() {
+      if (serverHeaderBuilder_ == null) {
+        if (payloadCase_ == 7) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 7) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        serverHeaderBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public io.grpc.binarylog.v1.ServerHeader.Builder getServerHeaderBuilder() {
+      return getServerHeaderFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    public io.grpc.binarylog.v1.ServerHeaderOrBuilder getServerHeaderOrBuilder() {
+      if ((payloadCase_ == 7) && (serverHeaderBuilder_ != null)) {
+        return serverHeaderBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 7) {
+          return (io.grpc.binarylog.v1.ServerHeader) payload_;
+        }
+        return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.ServerHeader, io.grpc.binarylog.v1.ServerHeader.Builder, io.grpc.binarylog.v1.ServerHeaderOrBuilder> 
+        getServerHeaderFieldBuilder() {
+      if (serverHeaderBuilder_ == null) {
+        if (!(payloadCase_ == 7)) {
+          payload_ = io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+        }
+        serverHeaderBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.ServerHeader, io.grpc.binarylog.v1.ServerHeader.Builder, io.grpc.binarylog.v1.ServerHeaderOrBuilder>(
+                (io.grpc.binarylog.v1.ServerHeader) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 7;
+      onChanged();;
+      return serverHeaderBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Message, io.grpc.binarylog.v1.Message.Builder, io.grpc.binarylog.v1.MessageOrBuilder> messageBuilder_;
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public boolean hasMessage() {
+      return payloadCase_ == 8;
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public io.grpc.binarylog.v1.Message getMessage() {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 8) {
+          return (io.grpc.binarylog.v1.Message) payload_;
+        }
+        return io.grpc.binarylog.v1.Message.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 8) {
+          return messageBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1.Message.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public Builder setMessage(io.grpc.binarylog.v1.Message value) {
+      if (messageBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        messageBuilder_.setMessage(value);
+      }
+      payloadCase_ = 8;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public Builder setMessage(
+        io.grpc.binarylog.v1.Message.Builder builderForValue) {
+      if (messageBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        messageBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 8;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public Builder mergeMessage(io.grpc.binarylog.v1.Message value) {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 8 &&
+            payload_ != io.grpc.binarylog.v1.Message.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1.Message.newBuilder((io.grpc.binarylog.v1.Message) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 8) {
+          messageBuilder_.mergeFrom(value);
+        }
+        messageBuilder_.setMessage(value);
+      }
+      payloadCase_ = 8;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public Builder clearMessage() {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 8) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 8) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        messageBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public io.grpc.binarylog.v1.Message.Builder getMessageBuilder() {
+      return getMessageFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    public io.grpc.binarylog.v1.MessageOrBuilder getMessageOrBuilder() {
+      if ((payloadCase_ == 8) && (messageBuilder_ != null)) {
+        return messageBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 8) {
+          return (io.grpc.binarylog.v1.Message) payload_;
+        }
+        return io.grpc.binarylog.v1.Message.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Message message = 8;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Message, io.grpc.binarylog.v1.Message.Builder, io.grpc.binarylog.v1.MessageOrBuilder> 
+        getMessageFieldBuilder() {
+      if (messageBuilder_ == null) {
+        if (!(payloadCase_ == 8)) {
+          payload_ = io.grpc.binarylog.v1.Message.getDefaultInstance();
+        }
+        messageBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Message, io.grpc.binarylog.v1.Message.Builder, io.grpc.binarylog.v1.MessageOrBuilder>(
+                (io.grpc.binarylog.v1.Message) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 8;
+      onChanged();;
+      return messageBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Trailer, io.grpc.binarylog.v1.Trailer.Builder, io.grpc.binarylog.v1.TrailerOrBuilder> trailerBuilder_;
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public boolean hasTrailer() {
+      return payloadCase_ == 9;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public io.grpc.binarylog.v1.Trailer getTrailer() {
+      if (trailerBuilder_ == null) {
+        if (payloadCase_ == 9) {
+          return (io.grpc.binarylog.v1.Trailer) payload_;
+        }
+        return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 9) {
+          return trailerBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public Builder setTrailer(io.grpc.binarylog.v1.Trailer value) {
+      if (trailerBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        trailerBuilder_.setMessage(value);
+      }
+      payloadCase_ = 9;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public Builder setTrailer(
+        io.grpc.binarylog.v1.Trailer.Builder builderForValue) {
+      if (trailerBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        trailerBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 9;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public Builder mergeTrailer(io.grpc.binarylog.v1.Trailer value) {
+      if (trailerBuilder_ == null) {
+        if (payloadCase_ == 9 &&
+            payload_ != io.grpc.binarylog.v1.Trailer.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1.Trailer.newBuilder((io.grpc.binarylog.v1.Trailer) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 9) {
+          trailerBuilder_.mergeFrom(value);
+        }
+        trailerBuilder_.setMessage(value);
+      }
+      payloadCase_ = 9;
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public Builder clearTrailer() {
+      if (trailerBuilder_ == null) {
+        if (payloadCase_ == 9) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 9) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        trailerBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public io.grpc.binarylog.v1.Trailer.Builder getTrailerBuilder() {
+      return getTrailerFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    public io.grpc.binarylog.v1.TrailerOrBuilder getTrailerOrBuilder() {
+      if ((payloadCase_ == 9) && (trailerBuilder_ != null)) {
+        return trailerBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 9) {
+          return (io.grpc.binarylog.v1.Trailer) payload_;
+        }
+        return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Trailer, io.grpc.binarylog.v1.Trailer.Builder, io.grpc.binarylog.v1.TrailerOrBuilder> 
+        getTrailerFieldBuilder() {
+      if (trailerBuilder_ == null) {
+        if (!(payloadCase_ == 9)) {
+          payload_ = io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+        }
+        trailerBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Trailer, io.grpc.binarylog.v1.Trailer.Builder, io.grpc.binarylog.v1.TrailerOrBuilder>(
+                (io.grpc.binarylog.v1.Trailer) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 9;
+      onChanged();;
+      return trailerBuilder_;
+    }
+
+    private boolean payloadTruncated_ ;
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool payload_truncated = 10;</code>
+     */
+    public boolean getPayloadTruncated() {
+      return payloadTruncated_;
+    }
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool payload_truncated = 10;</code>
+     */
+    public Builder setPayloadTruncated(boolean value) {
+      
+      payloadTruncated_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool payload_truncated = 10;</code>
+     */
+    public Builder clearPayloadTruncated() {
+      
+      payloadTruncated_ = false;
+      onChanged();
+      return this;
+    }
+
+    private io.grpc.binarylog.v1.Address peer_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Address, io.grpc.binarylog.v1.Address.Builder, io.grpc.binarylog.v1.AddressOrBuilder> peerBuilder_;
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public boolean hasPeer() {
+      return peerBuilder_ != null || peer_ != null;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public io.grpc.binarylog.v1.Address getPeer() {
+      if (peerBuilder_ == null) {
+        return peer_ == null ? io.grpc.binarylog.v1.Address.getDefaultInstance() : peer_;
+      } else {
+        return peerBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public Builder setPeer(io.grpc.binarylog.v1.Address 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 on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public Builder setPeer(
+        io.grpc.binarylog.v1.Address.Builder builderForValue) {
+      if (peerBuilder_ == null) {
+        peer_ = builderForValue.build();
+        onChanged();
+      } else {
+        peerBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public Builder mergePeer(io.grpc.binarylog.v1.Address value) {
+      if (peerBuilder_ == null) {
+        if (peer_ != null) {
+          peer_ =
+            io.grpc.binarylog.v1.Address.newBuilder(peer_).mergeFrom(value).buildPartial();
+        } else {
+          peer_ = value;
+        }
+        onChanged();
+      } else {
+        peerBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</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 on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public io.grpc.binarylog.v1.Address.Builder getPeerBuilder() {
+      
+      onChanged();
+      return getPeerFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    public io.grpc.binarylog.v1.AddressOrBuilder getPeerOrBuilder() {
+      if (peerBuilder_ != null) {
+        return peerBuilder_.getMessageOrBuilder();
+      } else {
+        return peer_ == null ?
+            io.grpc.binarylog.v1.Address.getDefaultInstance() : peer_;
+      }
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded on the first
+     * incoming event. On client side, peer is logged on
+     * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+     * the case of trailers-only. On server side, peer is always
+     * logged on EVENT_TYPE_CLIENT_HEADER.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Address, io.grpc.binarylog.v1.Address.Builder, io.grpc.binarylog.v1.AddressOrBuilder> 
+        getPeerFieldBuilder() {
+      if (peerBuilder_ == null) {
+        peerBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Address, io.grpc.binarylog.v1.Address.Builder, io.grpc.binarylog.v1.AddressOrBuilder>(
+                getPeer(),
+                getParentForChildren(),
+                isClean());
+        peer_ = null;
+      }
+      return peerBuilder_;
+    }
+    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.v1.GrpcLogEntry)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.GrpcLogEntry)
+  private static final io.grpc.binarylog.v1.GrpcLogEntry DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.GrpcLogEntry();
+  }
+
+  public static io.grpc.binarylog.v1.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.v1.GrpcLogEntry getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntryOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntryOrBuilder.java
new file mode 100644
index 0000000..46fb28f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/GrpcLogEntryOrBuilder.java
@@ -0,0 +1,197 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface GrpcLogEntryOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.GrpcLogEntry)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The timestamp of the binary log message
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  boolean hasTimestamp();
+  /**
+   * <pre>
+   * The timestamp of the binary log message
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  com.google.protobuf.Timestamp getTimestamp();
+  /**
+   * <pre>
+   * The timestamp of the binary log message
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * Uniquely identifies a call. The value must not be 0 in order to disambiguate
+   * from an unset value.
+   * Each call may have several log entries, they will all have the same call_id.
+   * Nothing is guaranteed about their value other than they are unique across
+   * different RPCs in the same gRPC process.
+   * </pre>
+   *
+   * <code>uint64 call_id = 2;</code>
+   */
+  long getCallId();
+
+  /**
+   * <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>uint64 sequence_id_within_call = 3;</code>
+   */
+  long getSequenceIdWithinCall();
+
+  /**
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+   */
+  int getTypeValue();
+  /**
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.EventType type = 4;</code>
+   */
+  io.grpc.binarylog.v1.GrpcLogEntry.EventType getType();
+
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+   */
+  int getLoggerValue();
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.GrpcLogEntry.Logger logger = 5;</code>
+   */
+  io.grpc.binarylog.v1.GrpcLogEntry.Logger getLogger();
+
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  boolean hasClientHeader();
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  io.grpc.binarylog.v1.ClientHeader getClientHeader();
+  /**
+   * <code>.grpc.binarylog.v1.ClientHeader client_header = 6;</code>
+   */
+  io.grpc.binarylog.v1.ClientHeaderOrBuilder getClientHeaderOrBuilder();
+
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  boolean hasServerHeader();
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  io.grpc.binarylog.v1.ServerHeader getServerHeader();
+  /**
+   * <code>.grpc.binarylog.v1.ServerHeader server_header = 7;</code>
+   */
+  io.grpc.binarylog.v1.ServerHeaderOrBuilder getServerHeaderOrBuilder();
+
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  io.grpc.binarylog.v1.Message getMessage();
+  /**
+   * <pre>
+   * Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Message message = 8;</code>
+   */
+  io.grpc.binarylog.v1.MessageOrBuilder getMessageOrBuilder();
+
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  boolean hasTrailer();
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  io.grpc.binarylog.v1.Trailer getTrailer();
+  /**
+   * <code>.grpc.binarylog.v1.Trailer trailer = 9;</code>
+   */
+  io.grpc.binarylog.v1.TrailerOrBuilder getTrailerOrBuilder();
+
+  /**
+   * <pre>
+   * true if payload does not represent the full message or metadata.
+   * </pre>
+   *
+   * <code>bool payload_truncated = 10;</code>
+   */
+  boolean getPayloadTruncated();
+
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  boolean hasPeer();
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  io.grpc.binarylog.v1.Address getPeer();
+  /**
+   * <pre>
+   * Peer address information, will only be recorded on the first
+   * incoming event. On client side, peer is logged on
+   * EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+   * the case of trailers-only. On server side, peer is always
+   * logged on EVENT_TYPE_CLIENT_HEADER.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Address peer = 11;</code>
+   */
+  io.grpc.binarylog.v1.AddressOrBuilder getPeerOrBuilder();
+
+  public io.grpc.binarylog.v1.GrpcLogEntry.PayloadCase getPayloadCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/Message.java b/services/src/generated/main/java/io/grpc/binarylog/v1/Message.java
new file mode 100644
index 0000000..bdde8f3
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/Message.java
@@ -0,0 +1,552 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * <pre>
+ * Message payload, used by CLIENT_MESSAGE and SERVER_MESSAGE
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1.Message}
+ */
+public  final class Message extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.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() {
+    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: {
+
+            length_ = input.readUInt32();
+            break;
+          }
+          case 18: {
+
+            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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Message_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Message_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.Message.class, io.grpc.binarylog.v1.Message.Builder.class);
+  }
+
+  public static final int LENGTH_FIELD_NUMBER = 1;
+  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 = 1;</code>
+   */
+  public int getLength() {
+    return length_;
+  }
+
+  public static final int DATA_FIELD_NUMBER = 2;
+  private com.google.protobuf.ByteString data_;
+  /**
+   * <pre>
+   * May be truncated or omitted.
+   * </pre>
+   *
+   * <code>bytes data = 2;</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 (length_ != 0) {
+      output.writeUInt32(1, length_);
+    }
+    if (!data_.isEmpty()) {
+      output.writeBytes(2, data_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (length_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(1, length_);
+    }
+    if (!data_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(2, 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.v1.Message)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.Message other = (io.grpc.binarylog.v1.Message) obj;
+
+    boolean result = true;
+    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) + 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.v1.Message parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Message parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Message parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Message parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.Message parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Message parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Message parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.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 CLIENT_MESSAGE and SERVER_MESSAGE
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1.Message}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.Message)
+      io.grpc.binarylog.v1.MessageOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Message_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Message_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.Message.class, io.grpc.binarylog.v1.Message.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.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();
+      length_ = 0;
+
+      data_ = com.google.protobuf.ByteString.EMPTY;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Message_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.Message getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.Message.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.Message build() {
+      io.grpc.binarylog.v1.Message result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.Message buildPartial() {
+      io.grpc.binarylog.v1.Message result = new io.grpc.binarylog.v1.Message(this);
+      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.v1.Message) {
+        return mergeFrom((io.grpc.binarylog.v1.Message)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.Message other) {
+      if (other == io.grpc.binarylog.v1.Message.getDefaultInstance()) return this;
+      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.v1.Message parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.Message) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      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 = 1;</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 = 1;</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 = 1;</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 = 2;</code>
+     */
+    public com.google.protobuf.ByteString getData() {
+      return data_;
+    }
+    /**
+     * <pre>
+     * May be truncated or omitted.
+     * </pre>
+     *
+     * <code>bytes data = 2;</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 = 2;</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.v1.Message)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.Message)
+  private static final io.grpc.binarylog.v1.Message DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.Message();
+  }
+
+  public static io.grpc.binarylog.v1.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.v1.Message getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/MessageOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/MessageOrBuilder.java
new file mode 100644
index 0000000..084cd35
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/MessageOrBuilder.java
@@ -0,0 +1,28 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface MessageOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.Message)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <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 = 1;</code>
+   */
+  int getLength();
+
+  /**
+   * <pre>
+   * May be truncated or omitted.
+   * </pre>
+   *
+   * <code>bytes data = 2;</code>
+   */
+  com.google.protobuf.ByteString getData();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/Metadata.java b/services/src/generated/main/java/io/grpc/binarylog/v1/Metadata.java
new file mode 100644
index 0000000..56052d5
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/Metadata.java
@@ -0,0 +1,778 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * <pre>
+ * A list of metadata pairs, used in the payload of client header,
+ * server header, and server trailer.
+ * Implementations may omit some entries to honor the header limits
+ * of GRPC_BINARY_LOG_CONFIG.
+ * Header keys added by gRPC are omitted. To be more specific,
+ * 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-' (with exception of grpc-trace-bin)
+ *   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
+ * Implementations must always log grpc-trace-bin if it is present.
+ * Practically speaking it will only be visible on server side because
+ * grpc-trace-bin is managed by low level client side mechanisms
+ * inaccessible from the application level. On server side, the
+ * header is just a normal metadata key.
+ * The pair will not count towards the size limit.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1.Metadata}
+ */
+public  final class Metadata extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.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.v1.MetadataEntry>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            entry_.add(
+                input.readMessage(io.grpc.binarylog.v1.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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Metadata_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Metadata_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.Metadata.class, io.grpc.binarylog.v1.Metadata.Builder.class);
+  }
+
+  public static final int ENTRY_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.binarylog.v1.MetadataEntry> entry_;
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  public java.util.List<io.grpc.binarylog.v1.MetadataEntry> getEntryList() {
+    return entry_;
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.binarylog.v1.MetadataEntryOrBuilder> 
+      getEntryOrBuilderList() {
+    return entry_;
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  public int getEntryCount() {
+    return entry_.size();
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  public io.grpc.binarylog.v1.MetadataEntry getEntry(int index) {
+    return entry_.get(index);
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  public io.grpc.binarylog.v1.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.v1.Metadata)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.Metadata other = (io.grpc.binarylog.v1.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.v1.Metadata parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Metadata parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Metadata parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Metadata parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.Metadata parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Metadata parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.Metadata parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.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 header,
+   * server header, and server trailer.
+   * Implementations may omit some entries to honor the header limits
+   * of GRPC_BINARY_LOG_CONFIG.
+   * Header keys added by gRPC are omitted. To be more specific,
+   * 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-' (with exception of grpc-trace-bin)
+   *   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
+   * Implementations must always log grpc-trace-bin if it is present.
+   * Practically speaking it will only be visible on server side because
+   * grpc-trace-bin is managed by low level client side mechanisms
+   * inaccessible from the application level. On server side, the
+   * header is just a normal metadata key.
+   * The pair will not count towards the size limit.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1.Metadata}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.Metadata)
+      io.grpc.binarylog.v1.MetadataOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Metadata_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Metadata_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.Metadata.class, io.grpc.binarylog.v1.Metadata.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Metadata_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.Metadata getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.Metadata.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.Metadata build() {
+      io.grpc.binarylog.v1.Metadata result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.Metadata buildPartial() {
+      io.grpc.binarylog.v1.Metadata result = new io.grpc.binarylog.v1.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.v1.Metadata) {
+        return mergeFrom((io.grpc.binarylog.v1.Metadata)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.Metadata other) {
+      if (other == io.grpc.binarylog.v1.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.v1.Metadata parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.Metadata) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.binarylog.v1.MetadataEntry> entry_ =
+      java.util.Collections.emptyList();
+    private void ensureEntryIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        entry_ = new java.util.ArrayList<io.grpc.binarylog.v1.MetadataEntry>(entry_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.binarylog.v1.MetadataEntry, io.grpc.binarylog.v1.MetadataEntry.Builder, io.grpc.binarylog.v1.MetadataEntryOrBuilder> entryBuilder_;
+
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<io.grpc.binarylog.v1.MetadataEntry> getEntryList() {
+      if (entryBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(entry_);
+      } else {
+        return entryBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public int getEntryCount() {
+      if (entryBuilder_ == null) {
+        return entry_.size();
+      } else {
+        return entryBuilder_.getCount();
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataEntry getEntry(int index) {
+      if (entryBuilder_ == null) {
+        return entry_.get(index);
+      } else {
+        return entryBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder setEntry(
+        int index, io.grpc.binarylog.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder setEntry(
+        int index, io.grpc.binarylog.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(io.grpc.binarylog.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        int index, io.grpc.binarylog.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        io.grpc.binarylog.v1.MetadataEntry.Builder builderForValue) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        entry_.add(builderForValue.build());
+        onChanged();
+      } else {
+        entryBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        int index, io.grpc.binarylog.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public Builder addAllEntry(
+        java.lang.Iterable<? extends io.grpc.binarylog.v1.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.v1.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.v1.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.v1.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataEntry.Builder getEntryBuilder(
+        int index) {
+      return getEntryFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataEntryOrBuilder getEntryOrBuilder(
+        int index) {
+      if (entryBuilder_ == null) {
+        return entry_.get(index);  } else {
+        return entryBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.binarylog.v1.MetadataEntryOrBuilder> 
+         getEntryOrBuilderList() {
+      if (entryBuilder_ != null) {
+        return entryBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(entry_);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataEntry.Builder addEntryBuilder() {
+      return getEntryFieldBuilder().addBuilder(
+          io.grpc.binarylog.v1.MetadataEntry.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataEntry.Builder addEntryBuilder(
+        int index) {
+      return getEntryFieldBuilder().addBuilder(
+          index, io.grpc.binarylog.v1.MetadataEntry.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<io.grpc.binarylog.v1.MetadataEntry.Builder> 
+         getEntryBuilderList() {
+      return getEntryFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.binarylog.v1.MetadataEntry, io.grpc.binarylog.v1.MetadataEntry.Builder, io.grpc.binarylog.v1.MetadataEntryOrBuilder> 
+        getEntryFieldBuilder() {
+      if (entryBuilder_ == null) {
+        entryBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.binarylog.v1.MetadataEntry, io.grpc.binarylog.v1.MetadataEntry.Builder, io.grpc.binarylog.v1.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.v1.Metadata)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.Metadata)
+  private static final io.grpc.binarylog.v1.Metadata DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.Metadata();
+  }
+
+  public static io.grpc.binarylog.v1.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.v1.Metadata getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntry.java b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntry.java
new file mode 100644
index 0000000..ce48266
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntry.java
@@ -0,0 +1,585 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * <pre>
+ * A metadata key value pair
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1.MetadataEntry}
+ */
+public  final class MetadataEntry extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.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_ = "";
+    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: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            key_ = s;
+            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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_MetadataEntry_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_MetadataEntry_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.MetadataEntry.class, io.grpc.binarylog.v1.MetadataEntry.Builder.class);
+  }
+
+  public static final int KEY_FIELD_NUMBER = 1;
+  private volatile java.lang.Object key_;
+  /**
+   * <code>string key = 1;</code>
+   */
+  public java.lang.String getKey() {
+    java.lang.Object ref = key_;
+    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();
+      key_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string key = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getKeyBytes() {
+    java.lang.Object ref = key_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      key_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  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 (!getKeyBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 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 (!getKeyBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(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.v1.MetadataEntry)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.MetadataEntry other = (io.grpc.binarylog.v1.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.v1.MetadataEntry parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.MetadataEntry parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.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.v1.MetadataEntry parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.MetadataEntry parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.MetadataEntry parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.MetadataEntry parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.MetadataEntry parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.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.v1.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.v1.MetadataEntry}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.MetadataEntry)
+      io.grpc.binarylog.v1.MetadataEntryOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_MetadataEntry_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_MetadataEntry_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.MetadataEntry.class, io.grpc.binarylog.v1.MetadataEntry.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.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_ = "";
+
+      value_ = com.google.protobuf.ByteString.EMPTY;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_MetadataEntry_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.MetadataEntry getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.MetadataEntry.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.MetadataEntry build() {
+      io.grpc.binarylog.v1.MetadataEntry result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.MetadataEntry buildPartial() {
+      io.grpc.binarylog.v1.MetadataEntry result = new io.grpc.binarylog.v1.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.v1.MetadataEntry) {
+        return mergeFrom((io.grpc.binarylog.v1.MetadataEntry)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.MetadataEntry other) {
+      if (other == io.grpc.binarylog.v1.MetadataEntry.getDefaultInstance()) return this;
+      if (!other.getKey().isEmpty()) {
+        key_ = other.key_;
+        onChanged();
+      }
+      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.v1.MetadataEntry parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.MetadataEntry) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object key_ = "";
+    /**
+     * <code>string key = 1;</code>
+     */
+    public java.lang.String getKey() {
+      java.lang.Object ref = key_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        key_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string key = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getKeyBytes() {
+      java.lang.Object ref = key_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        key_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string key = 1;</code>
+     */
+    public Builder setKey(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      key_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string key = 1;</code>
+     */
+    public Builder clearKey() {
+      
+      key_ = getDefaultInstance().getKey();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string key = 1;</code>
+     */
+    public Builder setKeyBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      key_ = value;
+      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.v1.MetadataEntry)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.MetadataEntry)
+  private static final io.grpc.binarylog.v1.MetadataEntry DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.MetadataEntry();
+  }
+
+  public static io.grpc.binarylog.v1.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.v1.MetadataEntry getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntryOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntryOrBuilder.java
new file mode 100644
index 0000000..57b6b6f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataEntryOrBuilder.java
@@ -0,0 +1,24 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface MetadataEntryOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.MetadataEntry)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string key = 1;</code>
+   */
+  java.lang.String getKey();
+  /**
+   * <code>string key = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getKeyBytes();
+
+  /**
+   * <code>bytes value = 2;</code>
+   */
+  com.google.protobuf.ByteString getValue();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataOrBuilder.java
new file mode 100644
index 0000000..cd13fb1
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/MetadataOrBuilder.java
@@ -0,0 +1,33 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface MetadataOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.Metadata)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  java.util.List<io.grpc.binarylog.v1.MetadataEntry> 
+      getEntryList();
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  io.grpc.binarylog.v1.MetadataEntry getEntry(int index);
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  int getEntryCount();
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  java.util.List<? extends io.grpc.binarylog.v1.MetadataEntryOrBuilder> 
+      getEntryOrBuilderList();
+  /**
+   * <code>repeated .grpc.binarylog.v1.MetadataEntry entry = 1;</code>
+   */
+  io.grpc.binarylog.v1.MetadataEntryOrBuilder getEntryOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeader.java b/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeader.java
new file mode 100644
index 0000000..be4477a
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeader.java
@@ -0,0 +1,618 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * Protobuf type {@code grpc.binarylog.v1.ServerHeader}
+ */
+public  final class ServerHeader extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.ServerHeader)
+    ServerHeaderOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerHeader.newBuilder() to construct.
+  private ServerHeader(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerHeader() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerHeader(
+      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.binarylog.v1.Metadata.Builder subBuilder = null;
+            if (metadata_ != null) {
+              subBuilder = metadata_.toBuilder();
+            }
+            metadata_ = input.readMessage(io.grpc.binarylog.v1.Metadata.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(metadata_);
+              metadata_ = 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.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ServerHeader_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ServerHeader_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.ServerHeader.class, io.grpc.binarylog.v1.ServerHeader.Builder.class);
+  }
+
+  public static final int METADATA_FIELD_NUMBER = 1;
+  private io.grpc.binarylog.v1.Metadata metadata_;
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public boolean hasMetadata() {
+    return metadata_ != null;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.Metadata getMetadata() {
+    return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+    return getMetadata();
+  }
+
+  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 (metadata_ != null) {
+      output.writeMessage(1, getMetadata());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (metadata_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getMetadata());
+    }
+    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.v1.ServerHeader)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.ServerHeader other = (io.grpc.binarylog.v1.ServerHeader) obj;
+
+    boolean result = true;
+    result = result && (hasMetadata() == other.hasMetadata());
+    if (hasMetadata()) {
+      result = result && getMetadata()
+          .equals(other.getMetadata());
+    }
+    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 (hasMetadata()) {
+      hash = (37 * hash) + METADATA_FIELD_NUMBER;
+      hash = (53 * hash) + getMetadata().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1.ServerHeader parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader 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.v1.ServerHeader parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader 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.v1.ServerHeader parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader 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.v1.ServerHeader parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader 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.v1.ServerHeader parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.ServerHeader 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.v1.ServerHeader 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.binarylog.v1.ServerHeader}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.ServerHeader)
+      io.grpc.binarylog.v1.ServerHeaderOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ServerHeader_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ServerHeader_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.ServerHeader.class, io.grpc.binarylog.v1.ServerHeader.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.ServerHeader.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 (metadataBuilder_ == null) {
+        metadata_ = null;
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_ServerHeader_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.ServerHeader getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.ServerHeader.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.ServerHeader build() {
+      io.grpc.binarylog.v1.ServerHeader result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.ServerHeader buildPartial() {
+      io.grpc.binarylog.v1.ServerHeader result = new io.grpc.binarylog.v1.ServerHeader(this);
+      if (metadataBuilder_ == null) {
+        result.metadata_ = metadata_;
+      } else {
+        result.metadata_ = metadataBuilder_.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.v1.ServerHeader) {
+        return mergeFrom((io.grpc.binarylog.v1.ServerHeader)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.ServerHeader other) {
+      if (other == io.grpc.binarylog.v1.ServerHeader.getDefaultInstance()) return this;
+      if (other.hasMetadata()) {
+        mergeMetadata(other.getMetadata());
+      }
+      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.v1.ServerHeader parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.ServerHeader) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.binarylog.v1.Metadata metadata_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> metadataBuilder_;
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public boolean hasMetadata() {
+      return metadataBuilder_ != null || metadata_ != null;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata getMetadata() {
+      if (metadataBuilder_ == null) {
+        return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      } else {
+        return metadataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        metadata_ = value;
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(
+        io.grpc.binarylog.v1.Metadata.Builder builderForValue) {
+      if (metadataBuilder_ == null) {
+        metadata_ = builderForValue.build();
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder mergeMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (metadata_ != null) {
+          metadata_ =
+            io.grpc.binarylog.v1.Metadata.newBuilder(metadata_).mergeFrom(value).buildPartial();
+        } else {
+          metadata_ = value;
+        }
+        onChanged();
+      } else {
+        metadataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder clearMetadata() {
+      if (metadataBuilder_ == null) {
+        metadata_ = null;
+        onChanged();
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata.Builder getMetadataBuilder() {
+      
+      onChanged();
+      return getMetadataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+      if (metadataBuilder_ != null) {
+        return metadataBuilder_.getMessageOrBuilder();
+      } else {
+        return metadata_ == null ?
+            io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> 
+        getMetadataFieldBuilder() {
+      if (metadataBuilder_ == null) {
+        metadataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder>(
+                getMetadata(),
+                getParentForChildren(),
+                isClean());
+        metadata_ = null;
+      }
+      return metadataBuilder_;
+    }
+    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.v1.ServerHeader)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.ServerHeader)
+  private static final io.grpc.binarylog.v1.ServerHeader DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.ServerHeader();
+  }
+
+  public static io.grpc.binarylog.v1.ServerHeader getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerHeader>
+      PARSER = new com.google.protobuf.AbstractParser<ServerHeader>() {
+    public ServerHeader parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerHeader(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerHeader> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerHeader> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1.ServerHeader getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeaderOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeaderOrBuilder.java
new file mode 100644
index 0000000..077748b
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/ServerHeaderOrBuilder.java
@@ -0,0 +1,34 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface ServerHeaderOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.ServerHeader)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  boolean hasMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.Metadata getMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/Trailer.java b/services/src/generated/main/java/io/grpc/binarylog/v1/Trailer.java
new file mode 100644
index 0000000..270265f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/Trailer.java
@@ -0,0 +1,935 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+/**
+ * Protobuf type {@code grpc.binarylog.v1.Trailer}
+ */
+public  final class Trailer extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1.Trailer)
+    TrailerOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Trailer.newBuilder() to construct.
+  private Trailer(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Trailer() {
+    statusCode_ = 0;
+    statusMessage_ = "";
+    statusDetails_ = com.google.protobuf.ByteString.EMPTY;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Trailer(
+      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.binarylog.v1.Metadata.Builder subBuilder = null;
+            if (metadata_ != null) {
+              subBuilder = metadata_.toBuilder();
+            }
+            metadata_ = input.readMessage(io.grpc.binarylog.v1.Metadata.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(metadata_);
+              metadata_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 16: {
+
+            statusCode_ = input.readUInt32();
+            break;
+          }
+          case 26: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            statusMessage_ = s;
+            break;
+          }
+          case 34: {
+
+            statusDetails_ = 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.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Trailer_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Trailer_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1.Trailer.class, io.grpc.binarylog.v1.Trailer.Builder.class);
+  }
+
+  public static final int METADATA_FIELD_NUMBER = 1;
+  private io.grpc.binarylog.v1.Metadata metadata_;
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public boolean hasMetadata() {
+    return metadata_ != null;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.Metadata getMetadata() {
+    return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+  }
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+    return getMetadata();
+  }
+
+  public static final int STATUS_CODE_FIELD_NUMBER = 2;
+  private int statusCode_;
+  /**
+   * <pre>
+   * The gRPC status code.
+   * </pre>
+   *
+   * <code>uint32 status_code = 2;</code>
+   */
+  public int getStatusCode() {
+    return statusCode_;
+  }
+
+  public static final int STATUS_MESSAGE_FIELD_NUMBER = 3;
+  private volatile java.lang.Object statusMessage_;
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 3;</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 = 3;</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 = 4;
+  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 = 4;</code>
+   */
+  public com.google.protobuf.ByteString getStatusDetails() {
+    return statusDetails_;
+  }
+
+  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 (metadata_ != null) {
+      output.writeMessage(1, getMetadata());
+    }
+    if (statusCode_ != 0) {
+      output.writeUInt32(2, statusCode_);
+    }
+    if (!getStatusMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 3, statusMessage_);
+    }
+    if (!statusDetails_.isEmpty()) {
+      output.writeBytes(4, statusDetails_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (metadata_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getMetadata());
+    }
+    if (statusCode_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(2, statusCode_);
+    }
+    if (!getStatusMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, statusMessage_);
+    }
+    if (!statusDetails_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(4, statusDetails_);
+    }
+    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.v1.Trailer)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1.Trailer other = (io.grpc.binarylog.v1.Trailer) obj;
+
+    boolean result = true;
+    result = result && (hasMetadata() == other.hasMetadata());
+    if (hasMetadata()) {
+      result = result && getMetadata()
+          .equals(other.getMetadata());
+    }
+    result = result && (getStatusCode()
+        == other.getStatusCode());
+    result = result && getStatusMessage()
+        .equals(other.getStatusMessage());
+    result = result && getStatusDetails()
+        .equals(other.getStatusDetails());
+    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 (hasMetadata()) {
+      hash = (37 * hash) + METADATA_FIELD_NUMBER;
+      hash = (53 * hash) + getMetadata().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();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1.Trailer parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Trailer 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.v1.Trailer parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Trailer 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.v1.Trailer parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1.Trailer parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1.Trailer parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.Trailer 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.v1.Trailer parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.Trailer 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.v1.Trailer parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1.Trailer 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.v1.Trailer 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.binarylog.v1.Trailer}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1.Trailer)
+      io.grpc.binarylog.v1.TrailerOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Trailer_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Trailer_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1.Trailer.class, io.grpc.binarylog.v1.Trailer.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1.Trailer.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 (metadataBuilder_ == null) {
+        metadata_ = null;
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+      statusCode_ = 0;
+
+      statusMessage_ = "";
+
+      statusDetails_ = com.google.protobuf.ByteString.EMPTY;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1.BinaryLogProto.internal_static_grpc_binarylog_v1_Trailer_descriptor;
+    }
+
+    public io.grpc.binarylog.v1.Trailer getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1.Trailer.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1.Trailer build() {
+      io.grpc.binarylog.v1.Trailer result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1.Trailer buildPartial() {
+      io.grpc.binarylog.v1.Trailer result = new io.grpc.binarylog.v1.Trailer(this);
+      if (metadataBuilder_ == null) {
+        result.metadata_ = metadata_;
+      } else {
+        result.metadata_ = metadataBuilder_.build();
+      }
+      result.statusCode_ = statusCode_;
+      result.statusMessage_ = statusMessage_;
+      result.statusDetails_ = statusDetails_;
+      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.v1.Trailer) {
+        return mergeFrom((io.grpc.binarylog.v1.Trailer)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1.Trailer other) {
+      if (other == io.grpc.binarylog.v1.Trailer.getDefaultInstance()) return this;
+      if (other.hasMetadata()) {
+        mergeMetadata(other.getMetadata());
+      }
+      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());
+      }
+      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.v1.Trailer parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1.Trailer) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.binarylog.v1.Metadata metadata_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> metadataBuilder_;
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public boolean hasMetadata() {
+      return metadataBuilder_ != null || metadata_ != null;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata getMetadata() {
+      if (metadataBuilder_ == null) {
+        return metadata_ == null ? io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      } else {
+        return metadataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        metadata_ = value;
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder setMetadata(
+        io.grpc.binarylog.v1.Metadata.Builder builderForValue) {
+      if (metadataBuilder_ == null) {
+        metadata_ = builderForValue.build();
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder mergeMetadata(io.grpc.binarylog.v1.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (metadata_ != null) {
+          metadata_ =
+            io.grpc.binarylog.v1.Metadata.newBuilder(metadata_).mergeFrom(value).buildPartial();
+        } else {
+          metadata_ = value;
+        }
+        onChanged();
+      } else {
+        metadataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public Builder clearMetadata() {
+      if (metadataBuilder_ == null) {
+        metadata_ = null;
+        onChanged();
+      } else {
+        metadata_ = null;
+        metadataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.Metadata.Builder getMetadataBuilder() {
+      
+      onChanged();
+      return getMetadataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    public io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder() {
+      if (metadataBuilder_ != null) {
+        return metadataBuilder_.getMessageOrBuilder();
+      } else {
+        return metadata_ == null ?
+            io.grpc.binarylog.v1.Metadata.getDefaultInstance() : metadata_;
+      }
+    }
+    /**
+     * <pre>
+     * This contains only the metadata from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder> 
+        getMetadataFieldBuilder() {
+      if (metadataBuilder_ == null) {
+        metadataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1.Metadata, io.grpc.binarylog.v1.Metadata.Builder, io.grpc.binarylog.v1.MetadataOrBuilder>(
+                getMetadata(),
+                getParentForChildren(),
+                isClean());
+        metadata_ = null;
+      }
+      return metadataBuilder_;
+    }
+
+    private int statusCode_ ;
+    /**
+     * <pre>
+     * The gRPC status code.
+     * </pre>
+     *
+     * <code>uint32 status_code = 2;</code>
+     */
+    public int getStatusCode() {
+      return statusCode_;
+    }
+    /**
+     * <pre>
+     * The gRPC status code.
+     * </pre>
+     *
+     * <code>uint32 status_code = 2;</code>
+     */
+    public Builder setStatusCode(int value) {
+      
+      statusCode_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The gRPC status code.
+     * </pre>
+     *
+     * <code>uint32 status_code = 2;</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 = 3;</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 = 3;</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 = 3;</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 = 3;</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 = 3;</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 = 4;</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 = 4;</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 = 4;</code>
+     */
+    public Builder clearStatusDetails() {
+      
+      statusDetails_ = getDefaultInstance().getStatusDetails();
+      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.v1.Trailer)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1.Trailer)
+  private static final io.grpc.binarylog.v1.Trailer DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1.Trailer();
+  }
+
+  public static io.grpc.binarylog.v1.Trailer getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Trailer>
+      PARSER = new com.google.protobuf.AbstractParser<Trailer>() {
+    public Trailer parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Trailer(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Trailer> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Trailer> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1.Trailer getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1/TrailerOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1/TrailerOrBuilder.java
new file mode 100644
index 0000000..359e63d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1/TrailerOrBuilder.java
@@ -0,0 +1,73 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1/binarylog.proto
+
+package io.grpc.binarylog.v1;
+
+public interface TrailerOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1.Trailer)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  boolean hasMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.Metadata getMetadata();
+  /**
+   * <pre>
+   * This contains only the metadata from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1.Metadata metadata = 1;</code>
+   */
+  io.grpc.binarylog.v1.MetadataOrBuilder getMetadataOrBuilder();
+
+  /**
+   * <pre>
+   * The gRPC status code.
+   * </pre>
+   *
+   * <code>uint32 status_code = 2;</code>
+   */
+  int getStatusCode();
+
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 3;</code>
+   */
+  java.lang.String getStatusMessage();
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 3;</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 = 4;</code>
+   */
+  com.google.protobuf.ByteString getStatusDetails();
+}
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
index efe312c..cfb969b 100644
--- a/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponse.java
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponse.java
@@ -97,6 +97,14 @@
      * <code>NOT_SERVING = 2;</code>
      */
     NOT_SERVING(2),
+    /**
+     * <pre>
+     * Used only by the Watch method.
+     * </pre>
+     *
+     * <code>SERVICE_UNKNOWN = 3;</code>
+     */
+    SERVICE_UNKNOWN(3),
     UNRECOGNIZED(-1),
     ;
 
@@ -112,6 +120,14 @@
      * <code>NOT_SERVING = 2;</code>
      */
     public static final int NOT_SERVING_VALUE = 2;
+    /**
+     * <pre>
+     * Used only by the Watch method.
+     * </pre>
+     *
+     * <code>SERVICE_UNKNOWN = 3;</code>
+     */
+    public static final int SERVICE_UNKNOWN_VALUE = 3;
 
 
     public final int getNumber() {
@@ -135,6 +151,7 @@
         case 0: return UNKNOWN;
         case 1: return SERVING;
         case 2: return NOT_SERVING;
+        case 3: return SERVICE_UNKNOWN;
         default: return null;
       }
     }
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
index ce0d1bd..8329cbe 100644
--- a/services/src/generated/main/java/io/grpc/health/v1/HealthProto.java
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthProto.java
@@ -35,14 +35,18 @@
     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(\t\"\251\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"
+      "e.ServingStatus\"O\n\rServingStatus\022\013\n\007UNKN" +
+      "OWN\020\000\022\013\n\007SERVING\020\001\022\017\n\013NOT_SERVING\020\002\022\023\n\017S" +
+      "ERVICE_UNKNOWN\020\0032\256\001\n\006Health\022P\n\005Check\022\".g" +
+      "rpc.health.v1.HealthCheckRequest\032#.grpc." +
+      "health.v1.HealthCheckResponse\022R\n\005Watch\022\"" +
+      ".grpc.health.v1.HealthCheckRequest\032#.grp" +
+      "c.health.v1.HealthCheckResponse0\001Ba\n\021io." +
+      "grpc.health.v1B\013HealthProtoP\001Z,google.go" +
+      "lang.org/grpc/health/grpc_health_v1\252\002\016Gr" +
+      "pc.Health.V1b\006proto3"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
diff --git a/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java b/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java
index 4202813..dee13a9 100644
--- a/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java
+++ b/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java
@@ -28,14 +28,20 @@
  * The default implementation of a {@link BinaryLogProvider}.
  */
 class BinaryLogProviderImpl extends BinaryLogProvider {
+  // avoid using 0 because proto3 long fields default to 0 when unset
+  private static final AtomicLong counter = new AtomicLong(1);
+
   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"));
   }
 
+  /**
+   * Deprecated and will be removed in a future version of gRPC.
+   */
+  @Deprecated
   public BinaryLogProviderImpl(BinaryLogSink sink) throws IOException {
     this(sink, System.getenv("GRPC_BINARY_LOG_CONFIG"));
   }
@@ -46,7 +52,7 @@
    * @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 {
+  public BinaryLogProviderImpl(BinaryLogSink sink, String configStr) throws IOException {
     this.sink = Preconditions.checkNotNull(sink);
     try {
       factory = new BinlogHelper.FactoryImpl(sink, configStr);
@@ -65,7 +71,7 @@
     if (helperForMethod == null) {
       return null;
     }
-    return helperForMethod.getServerInterceptor(getServerCallId());
+    return helperForMethod.getServerInterceptor(counter.getAndIncrement());
   }
 
   @Nullable
@@ -76,19 +82,11 @@
     if (helperForMethod == null) {
       return null;
     }
-    return helperForMethod.getClientInterceptor(getClientCallId(callOptions));
+    return helperForMethod.getClientInterceptor(counter.getAndIncrement());
   }
 
   @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/BinaryLogs.java b/services/src/main/java/io/grpc/services/BinaryLogs.java
index de7f791..fc2d8d8 100644
--- a/services/src/main/java/io/grpc/services/BinaryLogs.java
+++ b/services/src/main/java/io/grpc/services/BinaryLogs.java
@@ -32,11 +32,22 @@
   }
 
   /**
-   * Creates a binary log with a custom {@link BinaryLogSink} for receiving the logged data.
+   * Deprecated and will be removed in a future version of gRPC.
    */
+  @Deprecated
   public static BinaryLog createBinaryLog(BinaryLogSink sink) throws IOException {
     return new BinaryLogProviderImpl(sink);
   }
 
+  /**
+   * Creates a binary log with a custom {@link BinaryLogSink} for receiving the logged data,
+   * and a config string as defined by
+   * <a href="https://github.com/grpc/proposal/blob/master/A16-binary-logging.md">
+   *   A16-binary-logging</a>.
+   */
+  public static BinaryLog createBinaryLog(BinaryLogSink sink, String configStr) throws IOException {
+    return new BinaryLogProviderImpl(sink, configStr);
+  }
+
   private BinaryLogs() {}
 }
diff --git a/services/src/main/java/io/grpc/services/BinlogHelper.java b/services/src/main/java/io/grpc/services/BinlogHelper.java
index 2600167..ecdcc86 100644
--- a/services/src/main/java/io/grpc/services/BinlogHelper.java
+++ b/services/src/main/java/io/grpc/services/BinlogHelper.java
@@ -18,18 +18,20 @@
 
 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.services.BinaryLogProvider.BYTEARRAY_MARSHALLER;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
 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.protobuf.util.Timestamps;
 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;
@@ -50,14 +52,12 @@
 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 io.grpc.binarylog.v1.Address;
+import io.grpc.binarylog.v1.Address.Type;
+import io.grpc.binarylog.v1.GrpcLogEntry;
+import io.grpc.binarylog.v1.GrpcLogEntry.EventType;
+import io.grpc.binarylog.v1.Message;
+import io.grpc.binarylog.v1.Message.Builder;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -69,7 +69,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.annotation.Nullable;
@@ -81,8 +81,6 @@
 @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.
@@ -93,11 +91,6 @@
           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
@@ -108,134 +101,191 @@
   // TODO(zpencer): move proto related static helpers into this class
   static final class SinkWriterImpl extends SinkWriter {
     private final BinaryLogSink sink;
+    private TimeProvider timeProvider;
     private final int maxHeaderBytes;
     private final int maxMessageBytes;
 
-    SinkWriterImpl(BinaryLogSink sink, int maxHeaderBytes, int maxMessageBytes) {
+    private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1);
+
+    SinkWriterImpl(
+        BinaryLogSink sink,
+        TimeProvider timeProvider,
+        int maxHeaderBytes,
+        int maxMessageBytes) {
       this.sink = sink;
+      this.timeProvider = timeProvider;
       this.maxHeaderBytes = maxHeaderBytes;
       this.maxMessageBytes = maxMessageBytes;
     }
 
+    GrpcLogEntry.Builder newTimestampedBuilder() {
+      long epochNanos = timeProvider.currentTimeNanos();
+      return GrpcLogEntry.newBuilder().setTimestamp(Timestamps.fromNanos(epochNanos));
+    }
+
     @Override
-    void logSendInitialMetadata(
-        int seq,
-        @Nullable String methodName, // null on server
-        @Nullable Duration timeout, // null on server
+    void logClientHeader(
+        long seq,
+        String methodName,
+        // not all transports have the concept of authority
+        @Nullable String authority,
+        @Nullable Duration timeout,
         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);
-      }
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on client side
+        @Nullable SocketAddress peerAddress) {
+      Preconditions.checkArgument(methodName != null, "methodName can not be null");
+      Preconditions.checkArgument(
+          !methodName.startsWith("/"),
+          "in grpc-java method names should not have a leading '/'. However this class will "
+              + "add one to be consistent with language agnostic conventions.");
+      Preconditions.checkArgument(
+          peerAddress == null || logger == GrpcLogEntry.Logger.LOGGER_SERVER,
+          "peerSocket can only be specified for server");
+
+      MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair
+          = createMetadataProto(metadata, maxHeaderBytes);
+      io.grpc.binarylog.v1.ClientHeader.Builder clientHeaderBuilder
+          = io.grpc.binarylog.v1.ClientHeader.newBuilder()
+          .setMetadata(pair.proto)
+          .setMethodName("/" + methodName);
       if (timeout != null) {
-        entryBuilder.setTimeout(timeout);
+        clientHeaderBuilder.setTimeout(timeout);
+      }
+      if (authority != null) {
+        clientHeaderBuilder.setAuthority(authority);
+      }
+
+      GrpcLogEntry.Builder entryBuilder = newTimestampedBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(EventType.EVENT_TYPE_CLIENT_HEADER)
+          .setClientHeader(clientHeaderBuilder)
+          .setPayloadTruncated(pair.truncated)
+          .setLogger(logger)
+          .setCallId(callId);
+      if (peerAddress != null) {
+        entryBuilder.setPeer(socketToProto(peerAddress));
       }
       sink.write(entryBuilder.build());
     }
 
     @Override
-    void logRecvInitialMetadata(
-        int seq,
-        @Nullable String methodName, // null on client
-        @Nullable Duration timeout,  // null on client
+    void logServerHeader(
+        long seq,
         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()
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on server
+        @Nullable SocketAddress peerAddress) {
+      Preconditions.checkArgument(
+          peerAddress == null || logger == GrpcLogEntry.Logger.LOGGER_CLIENT,
+          "peerSocket can only be specified for client");
+      MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair
+          = createMetadataProto(metadata, maxHeaderBytes);
+
+      GrpcLogEntry.Builder entryBuilder = newTimestampedBuilder()
           .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);
+          .setType(EventType.EVENT_TYPE_SERVER_HEADER)
+          .setServerHeader(
+              io.grpc.binarylog.v1.ServerHeader.newBuilder()
+                  .setMetadata(pair.proto))
+          .setPayloadTruncated(pair.truncated)
+          .setLogger(logger)
+          .setCallId(callId);
+      if (peerAddress != null) {
+        entryBuilder.setPeer(socketToProto(peerAddress));
       }
       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());
+    void logTrailer(
+        long seq,
+        Status status,
+        Metadata metadata,
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on server, can be non null on client if this is a trailer-only response
+        @Nullable SocketAddress peerAddress) {
+      Preconditions.checkArgument(
+          peerAddress == null || logger == GrpcLogEntry.Logger.LOGGER_CLIENT,
+          "peerSocket can only be specified for client");
+      MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair
+          = createMetadataProto(metadata, maxHeaderBytes);
+
+      io.grpc.binarylog.v1.Trailer.Builder trailerBuilder
+          = io.grpc.binarylog.v1.Trailer.newBuilder()
+          .setStatusCode(status.getCode().value())
+          .setMetadata(pair.proto);
       String statusDescription = status.getDescription();
       if (statusDescription != null) {
-        entryBuilder.setStatusMessage(statusDescription);
+        trailerBuilder.setStatusMessage(statusDescription);
       }
       byte[] statusDetailBytes = metadata.get(STATUS_DETAILS_KEY);
       if (statusDetailBytes != null) {
-        entryBuilder.setStatusDetails(ByteString.copyFrom(statusDetailBytes));
+        trailerBuilder.setStatusDetails(ByteString.copyFrom(statusDetailBytes));
       }
 
-      addMetadataToProto(entryBuilder, metadata, maxHeaderBytes);
+      GrpcLogEntry.Builder entryBuilder = newTimestampedBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(EventType.EVENT_TYPE_SERVER_TRAILER)
+          .setTrailer(trailerBuilder)
+          .setPayloadTruncated(pair.truncated)
+          .setLogger(logger)
+          .setCallId(callId);
+      if (peerAddress != null) {
+        entryBuilder.setPeer(socketToProto(peerAddress));
+      }
       sink.write(entryBuilder.build());
     }
 
     @Override
-    <T> void logOutboundMessage(
-        int seq,
+    <T> void logRpcMessage(
+        long seq,
+        EventType eventType,
         Marshaller<T> marshaller,
         T message,
-        boolean compressed,
-        boolean isServer,
-        CallId callId) {
+        GrpcLogEntry.Logger logger,
+        long callId) {
+      Preconditions.checkArgument(
+          eventType == EventType.EVENT_TYPE_CLIENT_MESSAGE
+              || eventType == EventType.EVENT_TYPE_SERVER_MESSAGE,
+          "event type must correspond to client message or server message");
       if (marshaller != BYTEARRAY_MARSHALLER) {
         throw new IllegalStateException("Expected the BinaryLog's ByteArrayMarshaller");
       }
-      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+      MaybeTruncated<Builder> pair = createMessageProto((byte[]) message, maxMessageBytes);
+      GrpcLogEntry.Builder entryBuilder = newTimestampedBuilder()
           .setSequenceIdWithinCall(seq)
-          .setType(Type.SEND_MESSAGE)
-          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
-          .setCallId(callIdToProto(callId));
-      messageToProto(entryBuilder, (byte[]) message, compressed, maxMessageBytes);
+          .setType(eventType)
+          .setMessage(pair.proto)
+          .setPayloadTruncated(pair.truncated)
+          .setLogger(logger)
+          .setCallId(callId);
       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));
+    void logHalfClose(long seq, GrpcLogEntry.Logger logger, long callId) {
+      sink.write(
+          newTimestampedBuilder()
+              .setSequenceIdWithinCall(seq)
+              .setType(EventType.EVENT_TYPE_CLIENT_HALF_CLOSE)
+              .setLogger(logger)
+              .setCallId(callId)
+              .build());
+    }
 
-      messageToProto(entryBuilder, (byte[]) message, compressed, maxMessageBytes);
-      sink.write(entryBuilder.build());
+    @Override
+    void logCancel(long seq, GrpcLogEntry.Logger logger, long callId) {
+      sink.write(
+          newTimestampedBuilder()
+              .setSequenceIdWithinCall(seq)
+              .setType(EventType.EVENT_TYPE_CANCEL)
+              .setLogger(logger)
+              .setCallId(callId)
+              .build());
     }
 
     @Override
@@ -251,56 +301,64 @@
 
   abstract static class SinkWriter {
     /**
-     * Logs the sending of initial metadata. This method logs the appropriate number of bytes
+     * Logs the client header. This method logs the appropriate number of bytes
      * as determined by the binary logging configuration.
      */
-    abstract void logSendInitialMetadata(
-        int seq,
+    abstract void logClientHeader(
+        long seq,
         String methodName,
-        Duration timeout,
+        // not all transports have the concept of authority
+        @Nullable String authority,
+        @Nullable Duration timeout,
         Metadata metadata,
-        boolean isServer,
-        CallId callId);
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on client side
+        @Nullable SocketAddress peerAddress);
 
     /**
-     * Logs the receiving of initial metadata. This method logs the appropriate number of bytes
+     * Logs the server header. This method logs the appropriate number of bytes
      * as determined by the binary logging configuration.
      */
-    abstract void logRecvInitialMetadata(
-        int seq,
-        String methodName,
-        Duration timeout,
+    abstract void logServerHeader(
+        long seq,
         Metadata metadata,
-        boolean isServer,
-        CallId callId,
-        SocketAddress peerSocket);
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on server
+        @Nullable SocketAddress peerAddress);
 
     /**
-     * Logs the trailing metadata. This method logs the appropriate number of bytes
+     * Logs the server trailer. 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);
+    abstract void logTrailer(
+        long seq,
+        Status status,
+        Metadata metadata,
+        GrpcLogEntry.Logger logger,
+        long callId,
+        // null on server, can be non null on client if this is a trailer-only response
+        @Nullable SocketAddress peerAddress);
 
     /**
-     * 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}.
+     * Logs the message message. The number of bytes logged is determined by the binary
+     * logging configuration.
      */
-    abstract <T> void logOutboundMessage(
-        int seq, Marshaller<T> marshaller, T message, boolean compressed, boolean isServer,
-        CallId callId);
+    abstract <T> void logRpcMessage(
+        long seq,
+        EventType eventType,
+        Marshaller<T> marshaller,
+        T message,
+        GrpcLogEntry.Logger logger,
+        long callId);
+
+    abstract void logHalfClose(long seq, GrpcLogEntry.Logger logger, long 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}.
+     * Logs the cancellation.
      */
-    abstract <T> void logInboundMessage(
-        int seq, Marshaller<T> marshaller, T message, boolean compressed, boolean isServer,
-        CallId callId);
+    abstract void logCancel(long seq, GrpcLogEntry.Logger logger, long callId);
 
     /**
      * Returns the number bytes of the header this writer will log, according to configuration.
@@ -314,11 +372,7 @@
   }
 
   static SocketAddress getPeerSocket(Attributes streamAttributes) {
-    SocketAddress peer = streamAttributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
-    if (peer == null) {
-      return DUMMY_SOCKET;
-    }
-    return peer;
+    return streamAttributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
   }
 
   private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) {
@@ -331,56 +385,83 @@
     return deadline0.minimum(deadline1);
   }
 
-  public ClientInterceptor getClientInterceptor(final CallId callId) {
+  interface TimeProvider {
+    /** Returns the current nano time. */
+    long currentTimeNanos();
+
+    TimeProvider SYSTEM_TIME_PROVIDER = new TimeProvider() {
+      @Override
+      public long currentTimeNanos() {
+        return TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
+      }
+    };
+  }
+
+
+  public ClientInterceptor getClientInterceptor(final long callId) {
     return new ClientInterceptor() {
+      boolean trailersOnlyResponse = true;
       @Override
       public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
           final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
-        final AtomicInteger seq = new AtomicInteger(1);
+        final AtomicLong seq = new AtomicLong(1);
         final String methodName = method.getFullMethodName();
+        final String authority = next.authority();
         // 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) {
+          public void start(final 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);
+            writer.logClientHeader(
+                seq.getAndIncrement(),
+                methodName,
+                authority,
+                timeout,
+                headers,
+                GrpcLogEntry.Logger.LOGGER_CLIENT,
+                callId,
+                /*peerAddress=*/ null);
             ClientCall.Listener<RespT> wListener =
                 new SimpleForwardingClientCallListener<RespT>(responseListener) {
                   @Override
                   public void onMessage(RespT message) {
-                    writer.logInboundMessage(
+                    writer.logRpcMessage(
                         seq.getAndIncrement(),
+                        EventType.EVENT_TYPE_SERVER_MESSAGE,
                         method.getResponseMarshaller(),
                         message,
-                        DUMMY_IS_COMPRESSED,
-                        CLIENT,
+                        GrpcLogEntry.Logger.LOGGER_CLIENT,
                         callId);
                     super.onMessage(message);
                   }
 
                   @Override
                   public void onHeaders(Metadata headers) {
-                    SocketAddress peer = getPeerSocket(getAttributes());
-                    writer.logRecvInitialMetadata(
+                    trailersOnlyResponse = false;
+                    writer.logServerHeader(
                         seq.getAndIncrement(),
-                        /*methodName=*/ null,
-                        /*timeout=*/ null,
                         headers,
-                        CLIENT,
+                        GrpcLogEntry.Logger.LOGGER_CLIENT,
                         callId,
-                        peer);
+                        getPeerSocket(getAttributes()));
                     super.onHeaders(headers);
                   }
 
                   @Override
                   public void onClose(Status status, Metadata trailers) {
-                    writer.logTrailingMetadata(
-                        seq.getAndIncrement(), status, trailers, CLIENT, callId);
+                    SocketAddress peer = trailersOnlyResponse
+                        ? getPeerSocket(getAttributes()) : null;
+                    writer.logTrailer(
+                        seq.getAndIncrement(),
+                        status,
+                        trailers,
+                        GrpcLogEntry.Logger.LOGGER_CLIENT,
+                        callId,
+                        peer);
                     super.onClose(status, trailers);
                   }
                 };
@@ -389,64 +470,95 @@
 
           @Override
           public void sendMessage(ReqT message) {
-            writer.logOutboundMessage(
+            writer.logRpcMessage(
                 seq.getAndIncrement(),
+                EventType.EVENT_TYPE_CLIENT_MESSAGE,
                 method.getRequestMarshaller(),
                 message,
-                DUMMY_IS_COMPRESSED,
-                CLIENT,
+                GrpcLogEntry.Logger.LOGGER_CLIENT,
                 callId);
             super.sendMessage(message);
           }
+
+          @Override
+          public void halfClose() {
+            writer.logHalfClose(
+                seq.getAndIncrement(),
+                GrpcLogEntry.Logger.LOGGER_CLIENT,
+                callId);
+            super.halfClose();
+          }
+
+          @Override
+          public void cancel(String message, Throwable cause) {
+            writer.logCancel(
+                seq.getAndIncrement(),
+                GrpcLogEntry.Logger.LOGGER_CLIENT,
+                callId);
+            super.cancel(message, cause);
+          }
         };
       }
     };
   }
 
-  public ServerInterceptor getServerInterceptor(final CallId callId) {
+  public ServerInterceptor getServerInterceptor(final long 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);
+        final AtomicLong seq = new AtomicLong(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);
+        String authority = call.getAuthority();
+        Deadline deadline = Context.current().getDeadline();
+        final Duration timeout = deadline == null ? null
+            : Durations.fromNanos(deadline.timeRemaining(TimeUnit.NANOSECONDS));
 
-        writer.logRecvInitialMetadata(
-            seq.getAndIncrement(), methodName, timeout, headers, SERVER, callId, peer);
+        writer.logClientHeader(
+            seq.getAndIncrement(),
+            methodName,
+            authority,
+            timeout,
+            headers,
+            GrpcLogEntry.Logger.LOGGER_SERVER,
+            callId,
+            peer);
         ServerCall<ReqT, RespT> wCall = new SimpleForwardingServerCall<ReqT, RespT>(call) {
           @Override
           public void sendMessage(RespT message) {
-            writer.logOutboundMessage(
+            writer.logRpcMessage(
                 seq.getAndIncrement(),
+                EventType.EVENT_TYPE_SERVER_MESSAGE,
                 call.getMethodDescriptor().getResponseMarshaller(),
                 message,
-                DUMMY_IS_COMPRESSED,
-                SERVER,
+                GrpcLogEntry.Logger.LOGGER_SERVER,
                 callId);
             super.sendMessage(message);
           }
 
           @Override
           public void sendHeaders(Metadata headers) {
-            writer.logSendInitialMetadata(
+            writer.logServerHeader(
                 seq.getAndIncrement(),
-                /*methodName=*/ null,
-                /*timeout=*/ null,
                 headers,
-                SERVER,
-                callId);
+                GrpcLogEntry.Logger.LOGGER_SERVER,
+                callId,
+                /*peerAddress=*/ null);
             super.sendHeaders(headers);
           }
 
           @Override
           public void close(Status status, Metadata trailers) {
-            writer.logTrailingMetadata(seq.getAndIncrement(), status, trailers, SERVER, callId);
+            writer.logTrailer(
+                seq.getAndIncrement(),
+                status,
+                trailers,
+                GrpcLogEntry.Logger.LOGGER_SERVER,
+                callId,
+                /*peerAddress=*/ null);
             super.close(status, trailers);
           }
         };
@@ -454,15 +566,33 @@
         return new SimpleForwardingServerCallListener<ReqT>(next.startCall(wCall, headers)) {
           @Override
           public void onMessage(ReqT message) {
-            writer.logInboundMessage(
+            writer.logRpcMessage(
                 seq.getAndIncrement(),
+                EventType.EVENT_TYPE_CLIENT_MESSAGE,
                 call.getMethodDescriptor().getRequestMarshaller(),
                 message,
-                DUMMY_IS_COMPRESSED,
-                SERVER,
+                GrpcLogEntry.Logger.LOGGER_SERVER,
                 callId);
             super.onMessage(message);
           }
+
+          @Override
+          public void onHalfClose() {
+            writer.logHalfClose(
+                seq.getAndIncrement(),
+                GrpcLogEntry.Logger.LOGGER_SERVER,
+                callId);
+            super.onHalfClose();
+          }
+
+          @Override
+          public void onCancel() {
+            writer.logCancel(
+                seq.getAndIncrement(),
+                GrpcLogEntry.Logger.LOGGER_SERVER,
+                callId);
+            super.onCancel();
+          }
         };
       }
     };
@@ -507,47 +637,50 @@
         for (String configuration : Splitter.on(',').split(configurationString)) {
           Matcher configMatcher = configRe.matcher(configuration);
           if (!configMatcher.matches()) {
-            throw new IllegalArgumentException("Bad input: " + configuration);
+            throw new IllegalArgumentException("Illegal log config pattern: " + 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;
+            // parse config for "*"
+            checkState(
+                globalLog == null,
+                "Duplicate entry, this is fatal: " + configuration);
+            globalLog = createBinaryLog(sink, binlogOptionStr);
             logger.log(Level.INFO, "Global binlog: {0}", binlogOptionStr);
           } else if (isServiceGlob(methodOrSvc)) {
+            // parse config for a service, e.g. "service/*"
             String service = MethodDescriptor.extractFullServiceName(methodOrSvc);
-            if (perServiceLogs.containsKey(service)) {
-              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
-              continue;
-            }
-            perServiceLogs.put(service, binLog);
+            checkState(
+                !perServiceLogs.containsKey(service),
+                "Duplicate entry, this is fatal: " + configuration);
+            perServiceLogs.put(service, createBinaryLog(sink, binlogOptionStr));
             logger.log(
                 Level.INFO,
                 "Service binlog: service={0} config={1}",
                 new Object[] {service, binlogOptionStr});
           } else if (methodOrSvc.startsWith("-")) {
+            // parse config for a method, e.g. "-service/method"
             String blacklistedMethod = methodOrSvc.substring(1);
             if (blacklistedMethod.length() == 0) {
               continue;
             }
-            if (!blacklistedMethods.add(blacklistedMethod)) {
-              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
-            }
+            checkState(
+                !blacklistedMethods.contains(blacklistedMethod),
+                "Duplicate entry, this is fatal: " + configuration);
+            checkState(
+                !perMethodLogs.containsKey(blacklistedMethod),
+                "Duplicate entry, this is fatal: " + configuration);
+            blacklistedMethods.add(blacklistedMethod);
           } else {
-            // assume fully qualified method name
-            if (perMethodLogs.containsKey(methodOrSvc)) {
-              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
-              continue;
-            }
-            perMethodLogs.put(methodOrSvc, binLog);
+            // parse config for a fully qualified method, e.g "serice/method"
+            checkState(
+                !perMethodLogs.containsKey(methodOrSvc),
+                "Duplicate entry, this is fatal: " + configuration);
+            checkState(
+                !blacklistedMethods.contains(methodOrSvc),
+                "Duplicate entry, this method was blacklisted: " + configuration);
+            perMethodLogs.put(methodOrSvc, createBinaryLog(sink, binlogOptionStr));
             logger.log(
                 Level.INFO,
                 "Method binlog: method={0} config={1}",
@@ -595,7 +728,8 @@
     static BinlogHelper createBinaryLog(BinaryLogSink sink, @Nullable String logConfig) {
       if (logConfig == null) {
         return new BinlogHelper(
-            new SinkWriterImpl(sink, Integer.MAX_VALUE, Integer.MAX_VALUE));
+            new SinkWriterImpl(
+                sink, TimeProvider.SYSTEM_TIME_PROVIDER, Integer.MAX_VALUE, Integer.MAX_VALUE));
       }
       try {
         Matcher headerMatcher;
@@ -619,13 +753,13 @@
               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;
+          throw new IllegalArgumentException("Illegal log config pattern");
         }
-        return new BinlogHelper(new SinkWriterImpl(sink, maxHeaderBytes, maxMsgBytes));
+        return new BinlogHelper(
+            new SinkWriterImpl(
+                sink, TimeProvider.SYSTEM_TIME_PROVIDER, maxHeaderBytes, maxMsgBytes));
       } catch (NumberFormatException e) {
-        logger.log(Level.SEVERE, "Illegal log config pattern: " + logConfig);
-        return null;
+        throw new IllegalArgumentException("Illegal log config pattern");
       }
     }
 
@@ -637,30 +771,18 @@
     }
   }
 
-  /**
-   * 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) {
+  static Address socketToProto(SocketAddress address) {
     checkNotNull(address, "address");
 
-    Peer.Builder builder = Peer.newBuilder();
+    Address.Builder builder = Address.newBuilder();
     if (address instanceof InetSocketAddress) {
       InetAddress inetAddress = ((InetSocketAddress) address).getAddress();
       if (inetAddress instanceof Inet4Address) {
-        builder.setPeerType(PeerType.PEER_IPV4)
+        builder.setType(Type.TYPE_IPV4)
             .setAddress(InetAddressUtil.toAddrString(inetAddress));
       } else if (inetAddress instanceof Inet6Address) {
-        builder.setPeerType(PeerType.PEER_IPV6)
+        builder.setType(Type.TYPE_IPV6)
             .setAddress(InetAddressUtil.toAddrString(inetAddress));
       } else {
         logger.log(Level.SEVERE, "unknown type of InetSocketAddress: {}", address);
@@ -669,69 +791,79 @@
       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)
+      builder.setType(Type.TYPE_UNIX)
           .setAddress(address.toString());
     } else {
-      builder.setPeerType(PeerType.UNKNOWN_PEERTYPE).setAddress(address.toString());
+      builder.setType(Type.TYPE_UNKNOWN).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;
-        }
-      }
+  private static final Set<String> NEVER_INCLUDED_METADATA = new HashSet<String>(
+      Collections.singletonList(
+          // grpc-status-details-bin is already logged in a field of the binlog proto
+          STATUS_DETAILS_KEY.name()));
+  private static final Set<String> ALWAYS_INCLUDED_METADATA = new HashSet<String>(
+      Collections.singletonList(
+          "grpc-trace-bin"));
+
+  static final class MaybeTruncated<T> {
+    T proto;
+    boolean truncated;
+
+    private MaybeTruncated(T proto, boolean truncated) {
+      this.proto = proto;
+      this.truncated = truncated;
     }
-    // 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) {
+  static MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> createMetadataProto(
+      Metadata metadata, int maxHeaderBytes) {
+    checkNotNull(metadata, "metadata");
+    checkArgument(maxHeaderBytes >= 0, "maxHeaderBytes must be non negative");
+    io.grpc.binarylog.v1.Metadata.Builder metaBuilder = io.grpc.binarylog.v1.Metadata.newBuilder();
+    // This code is tightly coupled with Metadata's implementation
+    byte[][] serialized = InternalMetadata.serialize(metadata);
+    boolean truncated = false;
+    if (serialized != null) {
+      int curBytes = 0;
+      for (int i = 0; i < serialized.length; i += 2) {
+        String key = new String(serialized[i], Charsets.UTF_8);
+        byte[] value = serialized[i + 1];
+        if (NEVER_INCLUDED_METADATA.contains(key)) {
+          continue;
+        }
+        boolean forceInclude = ALWAYS_INCLUDED_METADATA.contains(key);
+        int bytesAfterAdd = curBytes + key.length() + value.length;
+        if (!forceInclude && bytesAfterAdd > maxHeaderBytes) {
+          truncated = true;
+          continue;
+        }
+        metaBuilder.addEntryBuilder()
+            .setKey(key)
+            .setValue(ByteString.copyFrom(value));
+        if (!forceInclude) {
+          // force included keys do not count towards the size limit
+          curBytes = bytesAfterAdd;
+        }
+      }
+    }
+    return new MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder>(metaBuilder, truncated);
+  }
+
+  @VisibleForTesting
+  static MaybeTruncated<Message.Builder> createMessageProto(
+      byte[] message, 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;
+    return new MaybeTruncated<Message.Builder>(msgBuilder, maxMessageBytes < message.length);
   }
 }
diff --git a/services/src/main/java/io/grpc/services/ChannelzService.java b/services/src/main/java/io/grpc/services/ChannelzService.java
index b8b3f1f..70702a8 100644
--- a/services/src/main/java/io/grpc/services/ChannelzService.java
+++ b/services/src/main/java/io/grpc/services/ChannelzService.java
@@ -61,7 +61,7 @@
     this.maxPageSize = maxPageSize;
   }
 
-  /** Returns top level channel aka {@link io.grpc.internal.ManagedChannelImpl}. */
+  /** Returns top level channel aka {@link io.grpc.ManagedChannel}. */
   @Override
   public void getTopChannels(
       GetTopChannelsRequest request, StreamObserver<GetTopChannelsResponse> responseObserver) {
@@ -72,7 +72,7 @@
     responseObserver.onCompleted();
   }
 
-  /** Returns a top level channel aka {@link io.grpc.internal.ManagedChannelImpl}. */
+  /** Returns a top level channel aka {@link io.grpc.ManagedChannel}. */
   @Override
   public void getChannel(
       GetChannelRequest request, StreamObserver<GetChannelResponse> responseObserver) {
diff --git a/services/src/main/proto/grpc/binlog/v1/binarylog.proto b/services/src/main/proto/grpc/binlog/v1/binarylog.proto
new file mode 100644
index 0000000..9ed1733
--- /dev/null
+++ b/services/src/main/proto/grpc/binlog/v1/binarylog.proto
@@ -0,0 +1,209 @@
+// 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.
+
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/binlog/v1/binarylog.proto
+
+syntax = "proto3";
+
+package grpc.binarylog.v1;
+
+import "google/protobuf/duration.proto";
+import "google/protobuf/timestamp.proto";
+
+option go_package = "google.golang.org/grpc/binarylog/grpc_binarylog_v1";
+option java_multiple_files = true;
+option java_package = "io.grpc.binarylog.v1";
+option java_outer_classname = "BinaryLogProto";
+
+// Log entry we store in binary logs
+message GrpcLogEntry {
+  // Enumerates the type of event
+  // Note the terminology is different from the RPC semantics
+  // definition, but the same meaning is expressed here.
+  enum EventType {
+    EVENT_TYPE_UNKNOWN = 0;
+    // Header sent from client to server
+    EVENT_TYPE_CLIENT_HEADER = 1;
+    // Header sent from server to client
+    EVENT_TYPE_SERVER_HEADER = 2;
+    // Message sent from client to server
+    EVENT_TYPE_CLIENT_MESSAGE = 3;
+    // Message sent from server to client
+    EVENT_TYPE_SERVER_MESSAGE = 4;
+    // A signal that client is done sending
+    EVENT_TYPE_CLIENT_HALF_CLOSE = 5;
+    // Trailer indicates the end of the RPC.
+    // On client side, this event means a trailer was either received
+    // from the network or the gRPC library locally generated a status
+    // to inform the application about a failure.
+    // On server side, this event means the server application requested
+    // to send a trailer. Note: EVENT_TYPE_CANCEL may still arrive after
+    // this due to races on server side.
+    EVENT_TYPE_SERVER_TRAILER = 6;
+    // A signal that the RPC is cancelled. On client side, this
+    // indicates the client application requests a cancellation.
+    // On server side, this indicates that cancellation was detected.
+    // Note: This marks the end of the RPC. Events may arrive after
+    // this due to races. For example, on client side a trailer
+    // may arrive even though the application requested to cancel the RPC.
+    EVENT_TYPE_CANCEL = 7;
+  }
+
+  // Enumerates the entity that generates the log entry
+  enum Logger {
+    LOGGER_UNKNOWN = 0;
+    LOGGER_CLIENT = 1;
+    LOGGER_SERVER = 2;
+  }
+
+  // The timestamp of the binary log message
+  google.protobuf.Timestamp timestamp = 1;
+
+  // Uniquely identifies a call. The value must not be 0 in order to disambiguate
+  // from an unset value.
+  // Each call may have several log entries, they will all have the same call_id.
+  // Nothing is guaranteed about their value other than they are unique across
+  // different RPCs in the same gRPC process.
+  uint64 call_id = 2;
+
+  // 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.
+  uint64 sequence_id_within_call = 3;
+
+  EventType type = 4;
+  Logger logger = 5;  // One of the above Logger enum
+
+  // The logger uses one of the following fields to record the payload,
+  // according to the type of the log entry.
+  oneof payload {
+    ClientHeader client_header = 6;
+    ServerHeader server_header = 7;
+    // Used by EVENT_TYPE_CLIENT_MESSAGE, EVENT_TYPE_SERVER_MESSAGE
+    Message message = 8;
+    Trailer trailer = 9;
+  }
+
+  // true if payload does not represent the full message or metadata.
+  bool payload_truncated = 10;
+
+  // Peer address information, will only be recorded on the first
+  // incoming event. On client side, peer is logged on
+  // EVENT_TYPE_SERVER_HEADER normally or EVENT_TYPE_SERVER_TRAILER in
+  // the case of trailers-only. On server side, peer is always
+  // logged on EVENT_TYPE_CLIENT_HEADER.
+  Address peer = 11;
+};
+
+message ClientHeader {
+  // This contains only the metadata from the application.
+  Metadata metadata = 1;
+
+  // The name of the RPC method, which looks something like:
+  // /<service>/<method>
+  // Note the leading "/" character.
+  string method_name = 2;
+
+  // A single process may be used to run multiple virtual
+  // servers with different identities.
+  // The authority is the name of such a server identitiy.
+  // It is typically a portion of the URI in the form of
+  // <host> or <host>:<port> .
+  string authority = 3;
+
+  // the RPC timeout
+  google.protobuf.Duration timeout = 4;
+}
+
+message ServerHeader {
+  // This contains only the metadata from the application.
+  Metadata metadata = 1;
+}
+
+message Trailer {
+  // This contains only the metadata from the application.
+  Metadata metadata = 1;
+
+  // The gRPC status code.
+  uint32 status_code = 2;
+
+  // An original status message before any transport specific
+  // encoding.
+  string status_message = 3;
+
+  // The value of the 'grpc-status-details-bin' metadata key. If
+  // present, this is always an encoded 'google.rpc.Status' message.
+  bytes status_details = 4;
+}
+
+// Message payload, used by CLIENT_MESSAGE and SERVER_MESSAGE
+message Message {
+  // 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 = 1;
+  // May be truncated or omitted.
+  bytes data = 2;
+}
+
+// A list of metadata pairs, used in the payload of client header,
+// server header, and server trailer.
+// Implementations may omit some entries to honor the header limits
+// of GRPC_BINARY_LOG_CONFIG.
+//
+// Header keys added by gRPC are omitted. To be more specific,
+// 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-' (with exception of grpc-trace-bin)
+//   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
+//
+// Implementations must always log grpc-trace-bin if it is present.
+// Practically speaking it will only be visible on server side because
+// grpc-trace-bin is managed by low level client side mechanisms
+// inaccessible from the application level. On server side, the
+// header is just a normal metadata key.
+// The pair will not count towards the size limit.
+message Metadata {
+  repeated MetadataEntry entry = 1;
+}
+
+// A metadata key value pair
+message MetadataEntry {
+  string key = 1;
+  bytes value = 2;
+}
+
+// Address information
+message Address {
+  enum Type {
+    TYPE_UNKNOWN = 0;
+    // address is in 1.2.3.4 form
+    TYPE_IPV4 = 1;
+    // address is in IPv6 canonical form (RFC5952 section 4)
+    // The scope is NOT included in the address string.
+    TYPE_IPV6 = 2;
+    // address is UDS string
+    TYPE_UNIX = 3;
+  };
+  Type type = 1;
+  string address = 2;
+  // only for TYPE_IPV4 and TYPE_IPV6
+  uint32 ip_port = 3;
+}
diff --git a/services/src/main/proto/grpc/health/v1/health.proto b/services/src/main/proto/grpc/health/v1/health.proto
index 44c4432..38843ff 100644
--- a/services/src/main/proto/grpc/health/v1/health.proto
+++ b/services/src/main/proto/grpc/health/v1/health.proto
@@ -12,11 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
+
 syntax = "proto3";
 
 package grpc.health.v1;
 
 option csharp_namespace = "Grpc.Health.V1";
+option go_package = "google.golang.org/grpc/health/grpc_health_v1";
 option java_multiple_files = true;
 option java_outer_classname = "HealthProto";
 option java_package = "io.grpc.health.v1";
@@ -30,10 +34,30 @@
     UNKNOWN = 0;
     SERVING = 1;
     NOT_SERVING = 2;
+    SERVICE_UNKNOWN = 3;  // Used only by the Watch method.
   }
   ServingStatus status = 1;
 }
 
 service Health {
+  // If the requested service is unknown, the call will fail with status
+  // NOT_FOUND.
   rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
+
+  // Performs a watch for the serving status of the requested service.
+  // The server will immediately send back a message indicating the current
+  // serving status.  It will then subsequently send a new message whenever
+  // the service's serving status changes.
+  //
+  // If the requested service is unknown when the call is received, the
+  // server will send a message setting the serving status to
+  // SERVICE_UNKNOWN but will *not* terminate the call.  If at some
+  // future point, the serving status of the service becomes known, the
+  // server will send a new message with the service's serving status.
+  //
+  // If the call terminates with status UNIMPLEMENTED, then clients
+  // should assume this method is not supported and should not retry the
+  // call.  If the call terminates with any other status (including OK),
+  // clients should retry the call with appropriate exponential backoff.
+  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
 }
diff --git a/services/src/test/java/io/grpc/services/BinlogHelperTest.java b/services/src/test/java/io/grpc/services/BinlogHelperTest.java
index cdaa836..5734f1d 100644
--- a/services/src/test/java/io/grpc/services/BinlogHelperTest.java
+++ b/services/src/test/java/io/grpc/services/BinlogHelperTest.java
@@ -18,13 +18,15 @@
 
 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.createMetadataProto;
 import static io.grpc.services.BinlogHelper.getPeerSocket;
 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.eq;
 import static org.mockito.Matchers.isNull;
@@ -33,12 +35,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.SettableFuture;
+import com.google.protobuf.Any;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.Duration;
+import com.google.protobuf.StringValue;
+import com.google.protobuf.Timestamp;
 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;
@@ -51,18 +56,25 @@
 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.StatusException;
+import io.grpc.binarylog.v1.Address;
+import io.grpc.binarylog.v1.Address.Type;
+import io.grpc.binarylog.v1.ClientHeader;
+import io.grpc.binarylog.v1.GrpcLogEntry;
+import io.grpc.binarylog.v1.GrpcLogEntry.EventType;
+import io.grpc.binarylog.v1.GrpcLogEntry.Logger;
+import io.grpc.binarylog.v1.Message;
+import io.grpc.binarylog.v1.MetadataEntry;
+import io.grpc.binarylog.v1.ServerHeader;
+import io.grpc.binarylog.v1.Trailer;
 import io.grpc.internal.NoopClientCall;
 import io.grpc.internal.NoopServerCall;
+import io.grpc.protobuf.StatusProto;
 import io.grpc.services.BinlogHelper.FactoryImpl;
+import io.grpc.services.BinlogHelper.MaybeTruncated;
 import io.grpc.services.BinlogHelper.SinkWriter;
 import io.grpc.services.BinlogHelper.SinkWriterImpl;
+import io.grpc.services.BinlogHelper.TimeProvider;
 import io.netty.channel.unix.DomainSocketAddress;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -101,35 +113,41 @@
   private static final MetadataEntry ENTRY_A =
       MetadataEntry
             .newBuilder()
-            .setKey(ByteString.copyFrom(KEY_A.name(), US_ASCII))
+            .setKey(KEY_A.name())
             .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))
+            .setKey(KEY_B.name())
             .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))
+            .setKey(KEY_C.name())
             .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 long CALL_ID = 0x1112131415161718L;
   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 Timestamp timestamp
+      = Timestamp.newBuilder().setSeconds(9876).setNanos(54321).build();
+  private final BinlogHelper.TimeProvider timeProvider = new TimeProvider() {
+    @Override
+    public long currentTimeNanos() {
+      return TimeUnit.SECONDS.toNanos(9876) + 54321;
+    }
+  };
   private final SinkWriter sinkWriterImpl =
-      new SinkWriterImpl(sink, HEADER_LIMIT, MESSAGE_LIMIT);
+      new SinkWriterImpl(
+          sink,
+          timeProvider,
+          HEADER_LIMIT,
+          MESSAGE_LIMIT);
   private final SinkWriter mockSinkWriter = mock(SinkWriter.class);
   private final byte[] message = new byte[100];
   private SocketAddress peer;
@@ -219,18 +237,37 @@
         makeLog("{h:256;m}"));
   }
 
+  private void assertIllegalPatternDetected(String perSvcOrMethodConfig) {
+    try {
+      FactoryImpl.createBinaryLog(sink, perSvcOrMethodConfig);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().startsWith("Illegal log config pattern");
+    }
+  }
+
+  @Test
+  public void badFactoryConfigStrDetected() throws Exception {
+    try {
+      new FactoryImpl(sink, "obviouslybad{");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      assertThat(expected).hasMessageThat().startsWith("Illegal log config pattern");
+    }
+  }
+
   @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}"));
+    assertIllegalPatternDetected("bad");
+    assertIllegalPatternDetected("{bad}");
+    assertIllegalPatternDetected("{x;y}");
+    assertIllegalPatternDetected("{h:abc}");
+    assertIllegalPatternDetected("{2}");
+    assertIllegalPatternDetected("{2;2}");
     // The grammar specifies that if both h and m are present, h comes before m
-    assertNull(makeLog("{m:123;h:123}"));
+    assertIllegalPatternDetected("{m:123;h:123}");
     // NumberFormatException
-    assertNull(makeLog("{h:99999999999999}"));
+    assertIllegalPatternDetected("{h:99999999999999}");
   }
 
   @Test
@@ -289,47 +326,32 @@
     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"));
+  private void assertDuplicatelPatternDetected(String factoryConfigStr) {
+    try {
+      new BinlogHelper.FactoryImpl(sink, factoryConfigStr);
+      fail();
+    } catch (IllegalStateException expected) {
+      assertThat(expected).hasMessageThat().startsWith("Duplicate entry");
+    }
   }
 
   @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"));
+  public void configBinLog_duplicates_global() throws Exception {
+    assertDuplicatelPatternDetected("*{h},*{h:256}");
   }
 
   @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"));
+  public void configBinLog_duplicates_service() throws Exception {
+    assertDuplicatelPatternDetected("p.s/*,p.s/*{h}");
+
   }
 
   @Test
-  public void callIdToProto() {
-    CallId callId = new CallId(0x1112131415161718L, 0x19101a1b1c1d1e1fL);
-    assertEquals(
-        Uint128
-            .newBuilder()
-            .setHigh(0x1112131415161718L)
-            .setLow(0x19101a1b1c1d1e1fL)
-            .build(),
-        BinlogHelper.callIdToProto(callId));
+  public void configBinLog_duplicates_method() throws Exception {
+    assertDuplicatelPatternDetected("p.s/*,p.s/*{h:1;m:2}");
+    assertDuplicatelPatternDetected("p.s/m,-p.s/m");
+    assertDuplicatelPatternDetected("-p.s/m,p.s/m");
+    assertDuplicatelPatternDetected("-p.s/m,-p.s/m");
   }
 
   @Test
@@ -338,9 +360,9 @@
     int port = 12345;
     InetSocketAddress socketAddress = new InetSocketAddress(address, port);
     assertEquals(
-        Peer
+        Address
             .newBuilder()
-            .setPeerType(Peer.PeerType.PEER_IPV4)
+            .setType(Type.TYPE_IPV4)
             .setAddress("127.0.0.1")
             .setIpPort(12345)
             .build(),
@@ -354,9 +376,9 @@
     int port = 12345;
     InetSocketAddress socketAddress = new InetSocketAddress(address, port);
     assertEquals(
-        Peer
+        Address
             .newBuilder()
-            .setPeerType(Peer.PeerType.PEER_IPV6)
+            .setType(Type.TYPE_IPV6)
             .setAddress("2001:db8::2:1") // RFC 5952 section 4: ipv6 canonical form required
             .setIpPort(12345)
             .build(),
@@ -368,9 +390,9 @@
     String path = "/some/path";
     DomainSocketAddress socketAddress = new DomainSocketAddress(path);
     assertEquals(
-        Peer
+        Address
             .newBuilder()
-            .setPeerType(Peer.PeerType.PEER_UNIX)
+            .setType(Type.TYPE_UNIX)
             .setAddress("/some/path")
             .build(),
         BinlogHelper.socketToProto(socketAddress)
@@ -386,8 +408,8 @@
       }
     };
     assertEquals(
-        Peer.newBuilder()
-            .setPeerType(PeerType.UNKNOWN_PEERTYPE)
+        Address.newBuilder()
+            .setType(Type.TYPE_UNKNOWN)
             .setAddress("some-socket-address")
             .build(),
         BinlogHelper.socketToProto(unknownSocket));
@@ -397,98 +419,86 @@
   public void metadataToProto_empty() throws Exception {
     assertEquals(
         GrpcLogEntry.newBuilder()
-            .setMetadata(io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance())
+            .setType(EventType.EVENT_TYPE_CLIENT_HEADER)
+            .setClientHeader(
+                ClientHeader.newBuilder().setMetadata(
+                    io.grpc.binarylog.v1.Metadata.getDefaultInstance()))
             .build(),
-        metadataToProtoTestHelper(new Metadata(), Integer.MAX_VALUE));
+        metadataToProtoTestHelper(
+            EventType.EVENT_TYPE_CLIENT_HEADER, new Metadata(), Integer.MAX_VALUE));
   }
 
   @Test
   public void metadataToProto() throws Exception {
     assertEquals(
         GrpcLogEntry.newBuilder()
-            .setMetadata(
-                io.grpc.binarylog.v1alpha.Metadata
+            .setType(EventType.EVENT_TYPE_CLIENT_HEADER)
+            .setClientHeader(
+                ClientHeader.newBuilder().setMetadata(
+                io.grpc.binarylog.v1.Metadata
                     .newBuilder()
                     .addEntry(ENTRY_A)
                     .addEntry(ENTRY_B)
                     .addEntry(ENTRY_C)
-                    .build())
+                    .build()))
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, Integer.MAX_VALUE));
+        metadataToProtoTestHelper(
+            EventType.EVENT_TYPE_CLIENT_HEADER, nonEmptyMetadata, Integer.MAX_VALUE));
+  }
+
+  @Test
+  public void metadataToProto_setsTruncated() throws Exception {
+    assertTrue(BinlogHelper.createMetadataProto(nonEmptyMetadata, 0).truncated);
   }
 
   @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));
+        io.grpc.binarylog.v1.Metadata.getDefaultInstance(),
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 0).proto.build());
     // not enough bytes for first key value
     assertEquals(
-        GrpcLogEntry.newBuilder()
-            .setMetadata(io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance())
-            .setTruncated(true)
-            .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 9));
+        io.grpc.binarylog.v1.Metadata.getDefaultInstance(),
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 9).proto.build());
     // enough for first key value
     assertEquals(
-        GrpcLogEntry.newBuilder()
-            .setMetadata(
-                io.grpc.binarylog.v1alpha.Metadata
-                    .newBuilder()
-                    .addEntry(ENTRY_A)
-                    .build())
-            .setTruncated(true)
+        io.grpc.binarylog.v1.Metadata
+            .newBuilder()
+            .addEntry(ENTRY_A)
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 10));
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 10).proto.build());
     // Test edge cases for >= 2 key values
     assertEquals(
-        GrpcLogEntry.newBuilder()
-            .setMetadata(
-                io.grpc.binarylog.v1alpha.Metadata
-                    .newBuilder()
-                    .addEntry(ENTRY_A)
-                    .build())
-            .setTruncated(true)
+        io.grpc.binarylog.v1.Metadata
+            .newBuilder()
+            .addEntry(ENTRY_A)
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 19));
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 19).proto.build());
     assertEquals(
-        GrpcLogEntry.newBuilder()
-            .setMetadata(
-                io.grpc.binarylog.v1alpha.Metadata
-                    .newBuilder()
-                    .addEntry(ENTRY_A)
-                    .addEntry(ENTRY_B)
-                    .build())
-            .setTruncated(true)
+        io.grpc.binarylog.v1.Metadata
+            .newBuilder()
+            .addEntry(ENTRY_A)
+            .addEntry(ENTRY_B)
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 20));
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 20).proto.build());
     assertEquals(
-        GrpcLogEntry.newBuilder()
-            .setMetadata(
-                io.grpc.binarylog.v1alpha.Metadata
-                    .newBuilder()
-                    .addEntry(ENTRY_A)
-                    .addEntry(ENTRY_B)
-                    .build())
-            .setTruncated(true)
+        io.grpc.binarylog.v1.Metadata
+            .newBuilder()
+            .addEntry(ENTRY_A)
+            .addEntry(ENTRY_B)
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 29));
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 29).proto.build());
+
     // 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())
+        io.grpc.binarylog.v1.Metadata
+            .newBuilder()
+            .addEntry(ENTRY_A)
+            .addEntry(ENTRY_B)
+            .addEntry(ENTRY_C)
             .build(),
-        metadataToProtoTestHelper(nonEmptyMetadata, 30));
+        BinlogHelper.createMetadataProto(nonEmptyMetadata, 30).proto.build());
   }
 
   @Test
@@ -501,11 +511,10 @@
                 Message
                     .newBuilder()
                     .setData(ByteString.copyFrom(bytes))
-                    .setFlags(0)
                     .setLength(bytes.length)
                     .build())
             .build(),
-        messageToProtoTestHelper(bytes, false, Integer.MAX_VALUE));
+        messageToProtoTestHelper(bytes, Integer.MAX_VALUE));
   }
 
   @Test
@@ -517,12 +526,11 @@
             .setMessage(
                 Message
                     .newBuilder()
-                    .setFlags(0)
                     .setLength(bytes.length)
                     .build())
-            .setTruncated(true)
+            .setPayloadTruncated(true)
             .build(),
-        messageToProtoTestHelper(bytes, false, 0));
+        messageToProtoTestHelper(bytes, 0));
 
     int limit = 10;
     String truncatedMessage = "this is a ";
@@ -532,312 +540,437 @@
                 Message
                     .newBuilder()
                     .setData(ByteString.copyFrom(truncatedMessage.getBytes(US_ASCII)))
-                    .setFlags(0)
                     .setLength(bytes.length)
                     .build())
-            .setTruncated(true)
+            .setPayloadTruncated(true)
             .build(),
-        messageToProtoTestHelper(bytes, false, limit));
+        messageToProtoTestHelper(bytes, 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 {
+  public void logClientHeader() throws Exception {
+    long seq = 1;
+    String authority = "authority";
+    String methodName = "service/method";
+    Duration timeout = Durations.fromMillis(1234);
     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());
+    InetSocketAddress peerAddress = new InetSocketAddress(address, port);
+    long callId = 1000;
 
-    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());
+    GrpcLogEntry.Builder builder =
+        metadataToProtoTestHelper(EventType.EVENT_TYPE_CLIENT_HEADER, nonEmptyMetadata, 10)
+            .toBuilder()
+            .setTimestamp(timestamp)
+            .setSequenceIdWithinCall(seq)
+            .setLogger(Logger.LOGGER_CLIENT)
+            .setCallId(callId);
+    builder.getClientHeaderBuilder()
+        .setMethodName("/" + methodName)
+        .setAuthority(authority)
+        .setTimeout(timeout);
+    GrpcLogEntry base = builder.build();
+    {
+      sinkWriterImpl.logClientHeader(
+          seq,
+          methodName,
+          authority,
+          timeout,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          /*peerAddress=*/ null);
+      verify(sink).write(base);
+    }
+
+    // logger is server
+    {
+      sinkWriterImpl.logClientHeader(
+          seq,
+          methodName,
+          authority,
+          timeout,
+          nonEmptyMetadata,
+          Logger.LOGGER_SERVER,
+          callId,
+          peerAddress);
+      verify(sink).write(
+          base.toBuilder()
+              .setPeer(BinlogHelper.socketToProto(peerAddress))
+              .setLogger(Logger.LOGGER_SERVER)
+              .build());
+    }
+
+    // authority is null
+    {
+      sinkWriterImpl.logClientHeader(
+          seq,
+          methodName,
+          /*authority=*/ null,
+          timeout,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          /*peerAddress=*/ null);
+
+      verify(sink).write(
+          base.toBuilder()
+              .setClientHeader(builder.getClientHeader().toBuilder().clearAuthority().build())
+              .build());
+    }
+
+    // timeout is null
+    {
+      sinkWriterImpl.logClientHeader(
+          seq,
+          methodName,
+          authority,
+          /*timeout=*/ null,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          /*peerAddress=*/ null);
+
+      verify(sink).write(
+          base.toBuilder()
+              .setClientHeader(builder.getClientHeader().toBuilder().clearTimeout().build())
+              .build());
+    }
+
+    // peerAddress is non null (error for client side)
+    try {
+      sinkWriterImpl.logClientHeader(
+          seq,
+          methodName,
+          authority,
+          timeout,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          peerAddress);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      // noop
+    }
   }
 
   @Test
-  public void logRecvInitialMetadata_client() throws Exception {
+  public void logServerHeader() throws Exception {
+    long seq = 1;
     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());
+    InetSocketAddress peerAddress = new InetSocketAddress(address, port);
+    long callId = 1000;
+
+    GrpcLogEntry.Builder builder =
+        metadataToProtoTestHelper(EventType.EVENT_TYPE_SERVER_HEADER, nonEmptyMetadata, 10)
+            .toBuilder()
+            .setTimestamp(timestamp)
+            .setSequenceIdWithinCall(seq)
+            .setLogger(Logger.LOGGER_CLIENT)
+            .setCallId(callId)
+            .setPeer(BinlogHelper.socketToProto(peerAddress));
+
+    {
+      sinkWriterImpl.logServerHeader(
+          seq,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          peerAddress);
+      verify(sink).write(builder.build());
+    }
+
+    // logger is server
+    // null peerAddress is required for server side
+    {
+      sinkWriterImpl.logServerHeader(
+          seq,
+          nonEmptyMetadata,
+          Logger.LOGGER_SERVER,
+          callId,
+          /*peerAddress=*/ null);
+      verify(sink).write(
+          builder
+              .setLogger(Logger.LOGGER_SERVER)
+              .clearPeer()
+              .build());
+    }
+
+    // logger is server
+    // non null peerAddress is an error
+    try {
+      sinkWriterImpl.logServerHeader(
+          seq,
+          nonEmptyMetadata,
+          Logger.LOGGER_SERVER,
+          callId,
+          peerAddress);
+      fail();
+    } catch (IllegalArgumentException expected) {
+      // noop
+    }
   }
 
   @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());
+  public void logTrailer() throws Exception {
+    long seq = 1;
+    InetAddress address = InetAddress.getByName("127.0.0.1");
+    int port = 12345;
+    InetSocketAddress peerAddress = new InetSocketAddress(address, port);
+    long callId = 1000;
+    Status statusDescription = Status.INTERNAL.withDescription("my description");
 
-    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());
+    GrpcLogEntry.Builder builder =
+        metadataToProtoTestHelper(EventType.EVENT_TYPE_SERVER_TRAILER, nonEmptyMetadata, 10)
+            .toBuilder()
+            .setTimestamp(timestamp)
+            .setSequenceIdWithinCall(seq)
+            .setLogger(Logger.LOGGER_CLIENT)
+            .setCallId(callId)
+            .setPeer(BinlogHelper.socketToProto(peerAddress));
+
+    builder.getTrailerBuilder()
+        .setStatusCode(Status.INTERNAL.getCode().value())
+        .setStatusMessage("my description");
+    GrpcLogEntry base = builder.build();
+
+    {
+      sinkWriterImpl.logTrailer(
+          seq,
+          statusDescription,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          peerAddress);
+      verify(sink).write(base);
+    }
+
+    // logger is server
+    {
+      sinkWriterImpl.logTrailer(
+          seq,
+          statusDescription,
+          nonEmptyMetadata,
+          Logger.LOGGER_SERVER,
+          callId,
+          /*peerAddress=*/ null);
+      verify(sink).write(
+          base.toBuilder()
+              .clearPeer()
+              .setLogger(Logger.LOGGER_SERVER)
+              .build());
+    }
+
+    // peerAddress is null
+    {
+      sinkWriterImpl.logTrailer(
+          seq,
+          statusDescription,
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          /*peerAddress=*/ null);
+      verify(sink).write(
+          base.toBuilder()
+              .clearPeer()
+              .build());
+    }
+
+    // status code is present but description is null
+    {
+      sinkWriterImpl.logTrailer(
+          seq,
+          statusDescription.getCode().toStatus(), // strip the description
+          nonEmptyMetadata,
+          Logger.LOGGER_CLIENT,
+          callId,
+          peerAddress);
+      verify(sink).write(
+          base.toBuilder()
+              .setTrailer(base.getTrailer().toBuilder().clearStatusMessage())
+              .build());
+    }
+
+    // status proto always logged if present (com.google.rpc.Status),
+    {
+      int zeroHeaderBytes = 0;
+      SinkWriterImpl truncatingWriter = new SinkWriterImpl(
+          sink, timeProvider, zeroHeaderBytes, MESSAGE_LIMIT);
+      com.google.rpc.Status statusProto = com.google.rpc.Status.newBuilder()
+          .addDetails(
+              Any.pack(StringValue.newBuilder().setValue("arbitrarypayload").build()))
+          .setCode(Status.INTERNAL.getCode().value())
+          .setMessage("status detail string")
+          .build();
+      StatusException statusException
+          = StatusProto.toStatusException(statusProto, nonEmptyMetadata);
+      truncatingWriter.logTrailer(
+          seq,
+          statusException.getStatus(),
+          statusException.getTrailers(),
+          Logger.LOGGER_CLIENT,
+          callId,
+          peerAddress);
+      verify(sink).write(
+          base.toBuilder()
+              .setTrailer(
+                  builder.getTrailerBuilder()
+                      .setStatusMessage("status detail string")
+                      .setStatusDetails(ByteString.copyFrom(statusProto.toByteArray()))
+                      .setMetadata(io.grpc.binarylog.v1.Metadata.getDefaultInstance()))
+              .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());
-
+  public void alwaysLoggedMetadata_grpcTraceBin() throws Exception {
+    Metadata.Key<byte[]> key
+        = Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER);
+    Metadata metadata = new Metadata();
+    metadata.put(key, new byte[1]);
+    int zeroHeaderBytes = 0;
+    MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair =
+        createMetadataProto(metadata, zeroHeaderBytes);
+    assertEquals(
+        key.name(),
+        Iterables.getOnlyElement(pair.proto.getEntryBuilderList()).getKey());
+    assertFalse(pair.truncated);
   }
 
   @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);
+  public void neverLoggedMetadata_grpcStatusDetilsBin() throws Exception {
+    Metadata.Key<byte[]> key
+        = Metadata.Key.of("grpc-status-details-bin", Metadata.BINARY_BYTE_MARSHALLER);
+    Metadata metadata = new Metadata();
+    metadata.put(key, new byte[1]);
+    int unlimitedHeaderBytes = Integer.MAX_VALUE;
+    MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair
+        = createMetadataProto(metadata, unlimitedHeaderBytes);
+    assertThat(pair.proto.getEntryBuilderList()).isEmpty();
+    assertFalse(pair.truncated);
   }
 
   @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());
+  public void logRpcMessage() throws Exception {
+    long seq = 1;
+    long callId = 1000;
+    GrpcLogEntry base = messageToProtoTestHelper(message, MESSAGE_LIMIT).toBuilder()
+        .setTimestamp(timestamp)
+        .setType(EventType.EVENT_TYPE_CLIENT_MESSAGE)
+        .setLogger(Logger.LOGGER_CLIENT)
+        .setSequenceIdWithinCall(1)
+        .setCallId(callId)
+        .build();
+    {
+      sinkWriterImpl.logRpcMessage(
+          seq,
+          EventType.EVENT_TYPE_CLIENT_MESSAGE,
+          BYTEARRAY_MARSHALLER,
+          message,
+          Logger.LOGGER_CLIENT,
+          callId);
+      verify(sink).write(base);
+    }
 
-    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);
-  }
+    // server messsage
+    {
+      sinkWriterImpl.logRpcMessage(
+          seq,
+          EventType.EVENT_TYPE_SERVER_MESSAGE,
+          BYTEARRAY_MARSHALLER,
+          message,
+          Logger.LOGGER_CLIENT,
+          callId);
+      verify(sink).write(
+          base.toBuilder()
+              .setType(EventType.EVENT_TYPE_SERVER_MESSAGE)
+              .build());
+    }
 
-  @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);
+    // logger is server
+    {
+      sinkWriterImpl.logRpcMessage(
+          seq,
+          EventType.EVENT_TYPE_CLIENT_MESSAGE,
+          BYTEARRAY_MARSHALLER,
+          message,
+          Logger.LOGGER_SERVER,
+          callId);
+      verify(sink).write(
+          base.toBuilder()
+              .setLogger(Logger.LOGGER_SERVER)
+              .build());
+    }
   }
 
   @Test
   public void getPeerSocketTest() {
-    assertSame(DUMMY_SOCKET, getPeerSocket(Attributes.EMPTY));
+    assertNull(getPeerSocket(Attributes.EMPTY));
     assertSame(
         peer,
         getPeerSocket(Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer).build()));
   }
 
   @Test
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public void serverDeadlineLogged() {
+    final AtomicReference<ServerCall> interceptedCall =
+        new AtomicReference<ServerCall>();
+    final ServerCall.Listener mockListener = mock(ServerCall.Listener.class);
+
+    final MethodDescriptor<byte[], byte[]> method =
+        MethodDescriptor.<byte[], byte[]>newBuilder()
+            .setType(MethodType.UNKNOWN)
+            .setFullMethodName("service/method")
+            .setRequestMarshaller(BYTEARRAY_MARSHALLER)
+            .setResponseMarshaller(BYTEARRAY_MARSHALLER)
+            .build();
+
+    // We expect the contents of the "grpc-timeout" header to be installed the context
+    Context.current()
+        .withDeadlineAfter(1, TimeUnit.SECONDS, Executors.newSingleThreadScheduledExecutor())
+        .run(new Runnable() {
+          @Override
+          public void run() {
+            ServerCall.Listener<byte[]> unused =
+                new BinlogHelper(mockSinkWriter)
+                    .getServerInterceptor(CALL_ID)
+                    .interceptCall(
+                        new NoopServerCall<byte[], byte[]>() {
+                          @Override
+                          public MethodDescriptor<byte[], byte[]> getMethodDescriptor() {
+                            return method;
+                          }
+                        },
+                        new Metadata(),
+                        new ServerCallHandler<byte[], byte[]>() {
+                          @Override
+                          public ServerCall.Listener<byte[]> startCall(
+                              ServerCall<byte[], byte[]> call,
+                              Metadata headers) {
+                            interceptedCall.set(call);
+                            return mockListener;
+                          }
+                        });
+          }
+        });
+    ArgumentCaptor<Duration> timeoutCaptor = ArgumentCaptor.forClass(Duration.class);
+    verify(mockSinkWriter).logClientHeader(
+        /*seq=*/ eq(1L),
+        eq("service/method"),
+        isNull(String.class),
+        timeoutCaptor.capture(),
+        any(Metadata.class),
+        eq(Logger.LOGGER_SERVER),
+        eq(CALL_ID),
+        isNull(SocketAddress.class));
+    verifyNoMoreInteractions(mockSinkWriter);
+    assertThat(TimeUnit.SECONDS.toNanos(1) - timeoutCaptor.getValue().getNanos())
+        .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
+  }
+
+  @Test
   @SuppressWarnings({"unchecked"})
   public void clientDeadlineLogged_deadlineSetViaCallOption() {
     MethodDescriptor<byte[], byte[]> method =
@@ -870,13 +1003,15 @@
                 });
     call.start(mockListener, new Metadata());
     ArgumentCaptor<Duration> callOptTimeoutCaptor = ArgumentCaptor.forClass(Duration.class);
-    verify(mockSinkWriter).logSendInitialMetadata(
+    verify(mockSinkWriter).logClientHeader(
         any(Integer.class),
         any(String.class),
+        any(String.class),
         callOptTimeoutCaptor.capture(),
         any(Metadata.class),
-        any(Boolean.class),
-        any(CallId.class));
+        any(GrpcLogEntry.Logger.class),
+        any(Long.class),
+        any(SocketAddress.class));
     Duration timeout = callOptTimeoutCaptor.getValue();
     assertThat(TimeUnit.SECONDS.toNanos(1) - Durations.toNanos(timeout))
         .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
@@ -924,13 +1059,15 @@
     ClientCall.Listener<byte[]> mockListener = mock(ClientCall.Listener.class);
     callFuture.get().start(mockListener, new Metadata());
     ArgumentCaptor<Duration> callOptTimeoutCaptor = ArgumentCaptor.forClass(Duration.class);
-    verify(mockSinkWriter).logSendInitialMetadata(
+    verify(mockSinkWriter).logClientHeader(
         any(Integer.class),
         any(String.class),
+        any(String.class),
         callOptTimeoutCaptor.capture(),
         any(Metadata.class),
-        any(Boolean.class),
-        any(CallId.class));
+        any(GrpcLogEntry.Logger.class),
+        any(Long.class),
+        any(SocketAddress.class));
     Duration timeout = callOptTimeoutCaptor.getValue();
     assertThat(TimeUnit.SECONDS.toNanos(1) - Durations.toNanos(timeout))
         .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
@@ -945,6 +1082,173 @@
     final AtomicReference<Metadata> actualClientInitial = new AtomicReference<Metadata>();
     final AtomicReference<Object> actualRequest = new AtomicReference<Object>();
 
+    final SettableFuture<Void> halfCloseCalled = SettableFuture.create();
+    final SettableFuture<Void> cancelCalled = SettableFuture.create();
+    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 void cancel(String message, Throwable cause) {
+            cancelCalled.set(null);
+          }
+
+          @Override
+          public void halfClose() {
+            halfCloseCalled.set(null);
+          }
+
+          @Override
+          public Attributes getAttributes() {
+            return Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer).build();
+          }
+        };
+      }
+
+      @Override
+      public String authority() {
+        return "the-authority";
+      }
+    };
+
+    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,
+                channel);
+
+    // send client header
+    {
+      Metadata clientInitial = new Metadata();
+      interceptedCall.start(mockListener, clientInitial);
+      verify(mockSinkWriter).logClientHeader(
+          /*seq=*/ eq(1L),
+          eq("service/method"),
+          eq("the-authority"),
+          isNull(Duration.class),
+          same(clientInitial),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID),
+          isNull(SocketAddress.class));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(clientInitial, actualClientInitial.get());
+    }
+
+    // receive server header
+    {
+      Metadata serverInitial = new Metadata();
+      interceptedListener.get().onHeaders(serverInitial);
+      verify(mockSinkWriter).logServerHeader(
+          /*seq=*/ eq(2L),
+          same(serverInitial),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID),
+          same(peer));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onHeaders(same(serverInitial));
+    }
+
+    // send client msg
+    {
+      byte[] request = "this is a request".getBytes(US_ASCII);
+      interceptedCall.sendMessage(request);
+      verify(mockSinkWriter).logRpcMessage(
+          /*seq=*/ eq(3L),
+          eq(EventType.EVENT_TYPE_CLIENT_MESSAGE),
+          same(BYTEARRAY_MARSHALLER),
+          same(request),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(request, actualRequest.get());
+    }
+
+    // client half close
+    {
+      interceptedCall.halfClose();
+      verify(mockSinkWriter).logHalfClose(
+          /*seq=*/ eq(4L),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID));
+      halfCloseCalled.get(1, TimeUnit.SECONDS);
+      verifyNoMoreInteractions(mockSinkWriter);
+    }
+
+    // receive server msg
+    {
+      byte[] response = "this is a response".getBytes(US_ASCII);
+      interceptedListener.get().onMessage(response);
+      verify(mockSinkWriter).logRpcMessage(
+          /*seq=*/ eq(5L),
+          eq(EventType.EVENT_TYPE_SERVER_MESSAGE),
+          same(BYTEARRAY_MARSHALLER),
+          same(response),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onMessage(same(response));
+    }
+
+    // receive trailer
+    {
+      Status status = Status.INTERNAL.withDescription("some description");
+      Metadata trailers = new Metadata();
+
+      interceptedListener.get().onClose(status, trailers);
+      verify(mockSinkWriter).logTrailer(
+          /*seq=*/ eq(6L),
+          same(status),
+          same(trailers),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID),
+          isNull(SocketAddress.class));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onClose(same(status), same(trailers));
+    }
+
+    // cancel
+    {
+      interceptedCall.cancel(null, null);
+      verify(mockSinkWriter).logCancel(
+          /*seq=*/ eq(7L),
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID));
+      cancelCalled.get(1, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public void clientInterceptor_trailersOnlyResponseLogsPeerAddress() 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(
@@ -970,7 +1274,7 @@
 
       @Override
       public String authority() {
-        throw new UnsupportedOperationException();
+        return "the-authority";
       }
     };
 
@@ -990,84 +1294,32 @@
                 method,
                 CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS),
                 channel);
+    Metadata clientInitial = new Metadata();
+    interceptedCall.start(mockListener, clientInitial);
+    verify(mockSinkWriter).logClientHeader(
+        /*seq=*/ eq(1L),
+        any(String.class),
+        any(String.class),
+        any(Duration.class),
+        any(Metadata.class),
+        eq(Logger.LOGGER_CLIENT),
+        eq(CALL_ID),
+        isNull(SocketAddress.class));
+    verifyNoMoreInteractions(mockSinkWriter);
 
-    // 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
+    // trailer only response
     {
       Status status = Status.INTERNAL.withDescription("some description");
       Metadata trailers = new Metadata();
 
       interceptedListener.get().onClose(status, trailers);
-      verify(mockSinkWriter).logTrailingMetadata(
-          /*seq=*/ eq(5),
+      verify(mockSinkWriter).logTrailer(
+          /*seq=*/ eq(2L),
           same(status),
           same(trailers),
-          eq(IS_CLIENT),
-          same(CALL_ID));
+          eq(Logger.LOGGER_CLIENT),
+          eq(CALL_ID),
+          same(peer));
       verifyNoMoreInteractions(mockSinkWriter);
       verify(mockListener).onClose(same(status), same(trailers));
     }
@@ -1086,10 +1338,9 @@
     final AtomicReference<Status> actualStatus = new AtomicReference<Status>();
     final AtomicReference<Metadata> actualTrailers = new AtomicReference<Metadata>();
 
-    // begin call and receive initial metadata
+    // begin call and receive client header
     {
       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)
@@ -1130,6 +1381,11 @@
                           .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer)
                           .build();
                     }
+
+                    @Override
+                    public String getAuthority() {
+                      return "the-authority";
+                    }
                   },
                   clientInitial,
                   new ServerCallHandler<byte[], byte[]>() {
@@ -1141,77 +1397,99 @@
                       return mockListener;
                     }
                   });
-      verify(mockSinkWriter).logRecvInitialMetadata(
-          /*seq=*/ eq(1),
+      verify(mockSinkWriter).logClientHeader(
+          /*seq=*/ eq(1L),
           eq("service/method"),
-          eq(Durations.fromMillis(1234)),
+          eq("the-authority"),
+          isNull(Duration.class),
           same(clientInitial),
-          eq(IS_SERVER),
-          same(CALL_ID),
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID),
           same(peer));
       verifyNoMoreInteractions(mockSinkWriter);
     }
 
-    // send initial metadata
+    // send server header
     {
       Metadata serverInital = new Metadata();
       interceptedCall.get().sendHeaders(serverInital);
-      verify(mockSinkWriter).logSendInitialMetadata(
-          /*seq=*/ eq(2),
-          isNull(String.class),
-          isNull(Duration.class),
+      verify(mockSinkWriter).logServerHeader(
+          /*seq=*/ eq(2L),
           same(serverInital),
-          eq(IS_SERVER),
-          same(CALL_ID));
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID),
+          isNull(SocketAddress.class));
       verifyNoMoreInteractions(mockSinkWriter);
       assertSame(serverInital, actualServerInitial.get());
     }
 
-    // receive request
+    // receive client msg
     {
       byte[] request = "this is a request".getBytes(US_ASCII);
       capturedListener.onMessage(request);
-      verify(mockSinkWriter).logInboundMessage(
-          /*seq=*/ eq(3),
+      verify(mockSinkWriter).logRpcMessage(
+          /*seq=*/ eq(3L),
+          eq(EventType.EVENT_TYPE_CLIENT_MESSAGE),
           same(BYTEARRAY_MARSHALLER),
           same(request),
-          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
-          eq(IS_SERVER),
-          same(CALL_ID));
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID));
       verifyNoMoreInteractions(mockSinkWriter);
       verify(mockListener).onMessage(same(request));
     }
 
-    // send response
+    // client half close
+    {
+      capturedListener.onHalfClose();
+      verify(mockSinkWriter).logHalfClose(
+          eq(4L),
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onHalfClose();
+    }
+
+    // send server msg
     {
       byte[] response = "this is a response".getBytes(US_ASCII);
       interceptedCall.get().sendMessage(response);
-      verify(mockSinkWriter).logOutboundMessage(
-          /*seq=*/ eq(4),
+      verify(mockSinkWriter).logRpcMessage(
+          /*seq=*/ eq(5L),
+          eq(EventType.EVENT_TYPE_SERVER_MESSAGE),
           same(BYTEARRAY_MARSHALLER),
           same(response),
-          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
-          eq(IS_SERVER),
-          same(CALL_ID));
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID));
       verifyNoMoreInteractions(mockSinkWriter);
       assertSame(response, actualResponse.get());
     }
 
-    // send trailers
+    // send trailer
     {
       Status status = Status.INTERNAL.withDescription("some description");
       Metadata trailers = new Metadata();
       interceptedCall.get().close(status, trailers);
-      verify(mockSinkWriter).logTrailingMetadata(
-          /*seq=*/ eq(5),
+      verify(mockSinkWriter).logTrailer(
+          /*seq=*/ eq(6L),
           same(status),
           same(trailers),
-          eq(IS_SERVER),
-          same(CALL_ID));
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID),
+          isNull(SocketAddress.class));
       verifyNoMoreInteractions(mockSinkWriter);
       assertSame(status, actualStatus.get());
       assertSame(trailers, actualTrailers.get());
     }
+
+    // cancel
+    {
+      capturedListener.onCancel();
+      verify(mockSinkWriter).logCancel(
+          /*seq=*/ eq(7L),
+          eq(Logger.LOGGER_SERVER),
+          eq(CALL_ID));
+      verify(mockListener).onCancel();
+    }
   }
 
   /** A builder class to make unit test code more readable. */
@@ -1231,7 +1509,7 @@
 
     BinlogHelper build() {
       return new BinlogHelper(
-          new SinkWriterImpl(mock(BinaryLogSink.class), maxHeaderBytes, maxMessageBytes));
+          new SinkWriterImpl(mock(BinaryLogSink.class), null, maxHeaderBytes, maxMessageBytes));
     }
   }
 
@@ -1249,16 +1527,33 @@
   }
 
   private static GrpcLogEntry metadataToProtoTestHelper(
-      Metadata metadata, int maxHeaderBytes) {
+      EventType type, Metadata metadata, int maxHeaderBytes) {
     GrpcLogEntry.Builder builder = GrpcLogEntry.newBuilder();
-    BinlogHelper.addMetadataToProto(builder, metadata, maxHeaderBytes);
+    MaybeTruncated<io.grpc.binarylog.v1.Metadata.Builder> pair
+        = BinlogHelper.createMetadataProto(metadata, maxHeaderBytes);
+    switch (type) {
+      case EVENT_TYPE_CLIENT_HEADER:
+        builder.setClientHeader(ClientHeader.newBuilder().setMetadata(pair.proto));
+        break;
+      case EVENT_TYPE_SERVER_HEADER:
+        builder.setServerHeader(ServerHeader.newBuilder().setMetadata(pair.proto));
+        break;
+      case EVENT_TYPE_SERVER_TRAILER:
+        builder.setTrailer(Trailer.newBuilder().setMetadata(pair.proto));
+        break;
+      default:
+        throw new IllegalArgumentException();
+    }
+    builder.setType(type).setPayloadTruncated(pair.truncated);
     return builder.build();
   }
 
   private static GrpcLogEntry messageToProtoTestHelper(
-      byte[] message, boolean compressed, int maxMessageBytes) {
+      byte[] message, int maxMessageBytes) {
     GrpcLogEntry.Builder builder = GrpcLogEntry.newBuilder();
-    BinlogHelper.messageToProto(builder, message, compressed, maxMessageBytes);
+    MaybeTruncated<Message.Builder> pair
+        = BinlogHelper.createMessageProto(message, maxMessageBytes);
+    builder.setMessage(pair.proto).setPayloadTruncated(pair.truncated);
     return builder.build();
   }
 }
diff --git a/testing/Android.bp b/testing/Android.bp
new file mode 100644
index 0000000..f4c2661
--- /dev/null
+++ b/testing/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2020 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.
+//
+//
+
+java_library_host {
+    name: "grpc-java-testing",
+    srcs: [
+        "src/main/java/io/grpc/testing/*.java",
+    ],
+    java_resource_dirs: [
+        "src/main/resources",
+    ],
+    libs: [
+        "grpc-java-context",
+        "grpc-java-core",
+        "grpc-java-core-inprocess",
+        "grpc-java-core-internal", // Not sure why this has to be here.
+        "grpc-java-core-util",
+        "grpc-java-stub",
+        "jsr305",
+        "guava",
+        "truth-prebuilt",
+        "junit",
+    ],
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java
index 2acbba9..19f5e1f 100644
--- a/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java
+++ b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java
@@ -44,7 +44,6 @@
 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;
@@ -59,6 +58,7 @@
 import io.grpc.internal.ClientStreamListener;
 import io.grpc.internal.ClientTransport;
 import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcAttributes;
 import io.grpc.internal.InternalServer;
 import io.grpc.internal.IoUtils;
 import io.grpc.internal.ManagedClientTransport;
@@ -346,7 +346,7 @@
     verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
 
     assertNotNull("security level should be set in client attributes",
-        connectionClient.getAttributes().get(CallCredentials.ATTR_SECURITY_LEVEL));
+        connectionClient.getAttributes().get(GrpcAttributes.ATTR_SECURITY_LEVEL));
   }
 
   @Test
@@ -707,6 +707,7 @@
     assertEquals("additional attribute value",
         serverStream.getAttributes().get(ADDITIONAL_TRANSPORT_ATTR_KEY));
     assertNotNull(serverStream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
+    assertNotNull(serverStream.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR));
 
     serverStream.request(1);
     assertTrue(clientStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));