xds: support for xDS data types needed for server side SDS support (#6718)

diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java
new file mode 100644
index 0000000..9d5e260
--- /dev/null
+++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.xds;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Defines gRPC data types for Envoy protobuf messages used in xDS protocol on the server side,
+ * similar to how {@link EnvoyProtoData} defines it for the client side.
+ */
+final class EnvoyServerProtoData {
+
+  // Prevent instantiation.
+  private EnvoyServerProtoData() {
+  }
+
+  static final class CidrRange {
+    private final String addressPrefix;
+    private final int prefixLen;
+
+    @VisibleForTesting
+    CidrRange(String addressPrefix, int prefixLen) {
+      this.addressPrefix = addressPrefix;
+      this.prefixLen = prefixLen;
+    }
+
+    static CidrRange fromEnvoyProtoCidrRange(
+        io.envoyproxy.envoy.api.v2.core.CidrRange proto) {
+      return new CidrRange(proto.getAddressPrefix(), proto.getPrefixLen().getValue());
+    }
+
+    public String getAddressPrefix() {
+      return addressPrefix;
+    }
+
+    public int getPrefixLen() {
+      return prefixLen;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      CidrRange cidrRange = (CidrRange) o;
+      return prefixLen == cidrRange.prefixLen
+          && java.util.Objects.equals(addressPrefix, cidrRange.addressPrefix);
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(addressPrefix, prefixLen);
+    }
+
+    @Override
+    public String toString() {
+      return "CidrRange{"
+          + "addressPrefix='" + addressPrefix + '\''
+          + ", prefixLen=" + prefixLen
+          + '}';
+    }
+  }
+
+  /**
+   * Corresponds to Envoy proto message
+   * {@link io.envoyproxy.envoy.api.v2.listener.FilterChainMatch}.
+   */
+  static final class FilterChainMatch {
+    private final int destinationPort;
+    private final List<CidrRange> prefixRanges;
+    private final List<String> applicationProtocols;
+
+    private FilterChainMatch(int destinationPort,
+        List<CidrRange> prefixRanges, List<String> applicationProtocols) {
+      this.destinationPort = destinationPort;
+      this.prefixRanges = Collections.unmodifiableList(prefixRanges);
+      this.applicationProtocols = Collections.unmodifiableList(applicationProtocols);
+    }
+
+    static FilterChainMatch fromEnvoyProtoFilterChainMatch(
+        io.envoyproxy.envoy.api.v2.listener.FilterChainMatch proto) {
+      List<CidrRange> prefixRanges = new ArrayList<>();
+      for (io.envoyproxy.envoy.api.v2.core.CidrRange range : proto.getPrefixRangesList()) {
+        prefixRanges.add(CidrRange.fromEnvoyProtoCidrRange(range));
+      }
+      List<String> applicationProtocols = new ArrayList<>();
+      for (String appProtocol  : proto.getApplicationProtocolsList()) {
+        applicationProtocols.add(appProtocol);
+      }
+      return new FilterChainMatch(
+          proto.getDestinationPort().getValue(),
+          prefixRanges,
+          applicationProtocols);
+    }
+
+    public int getDestinationPort() {
+      return destinationPort;
+    }
+
+    public List<CidrRange> getPrefixRanges() {
+      return prefixRanges;
+    }
+
+    public List<String> getApplicationProtocols() {
+      return applicationProtocols;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FilterChainMatch that = (FilterChainMatch) o;
+      return destinationPort == that.destinationPort
+          && java.util.Objects.equals(prefixRanges, that.prefixRanges)
+          && java.util.Objects.equals(applicationProtocols, that.applicationProtocols);
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(destinationPort, prefixRanges, applicationProtocols);
+    }
+
+    @Override
+    public String toString() {
+      return "FilterChainMatch{"
+          + "destinationPort=" + destinationPort
+          + ", prefixRanges=" + prefixRanges
+          + ", applicationProtocols=" + applicationProtocols
+          + '}';
+    }
+  }
+
+  /**
+   * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.listener.FilterChain}.
+   */
+  static final class FilterChain {
+    private final FilterChainMatch filterChainMatch;
+    // TODO(sanjaypujare): remove dependency on envoy data type along with rest of the code.
+    private final io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext downstreamTlsContext;
+
+    private FilterChain(FilterChainMatch filterChainMatch,
+        io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext downstreamTlsContext) {
+      this.filterChainMatch = filterChainMatch;
+      this.downstreamTlsContext = downstreamTlsContext;
+    }
+
+    static FilterChain fromEnvoyProtoFilterChain(
+        io.envoyproxy.envoy.api.v2.listener.FilterChain proto) {
+      return new FilterChain(
+          FilterChainMatch.fromEnvoyProtoFilterChainMatch(proto.getFilterChainMatch()),
+          proto.getTlsContext()
+      );
+    }
+
+    public FilterChainMatch getFilterChainMatch() {
+      return filterChainMatch;
+    }
+
+    public io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext getDownstreamTlsContext() {
+      return downstreamTlsContext;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FilterChain that = (FilterChain) o;
+      return java.util.Objects.equals(filterChainMatch, that.filterChainMatch)
+          && java.util.Objects.equals(downstreamTlsContext, that.downstreamTlsContext);
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(filterChainMatch, downstreamTlsContext);
+    }
+
+    @Override
+    public String toString() {
+      return "FilterChain{"
+          + "filterChainMatch=" + filterChainMatch
+          + ", downstreamTlsContext=" + downstreamTlsContext
+          + '}';
+    }
+  }
+
+  /**
+   * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.Listener} & related
+   * classes.
+   */
+  static final class Listener {
+    private final String name;
+    @Nullable
+    private final String address;
+    private final List<FilterChain> filterChains;
+
+    private Listener(String name, String address,
+        List<FilterChain> filterChains) {
+      this.name = name;
+      this.address = address;
+      this.filterChains = Collections.unmodifiableList(filterChains);
+    }
+
+    private static String convertEnvoyAddressToString(
+        io.envoyproxy.envoy.api.v2.core.Address proto) {
+      if (proto.hasSocketAddress()) {
+        io.envoyproxy.envoy.api.v2.core.SocketAddress socketAddress = proto.getSocketAddress();
+        String address = socketAddress.getAddress();
+        switch (socketAddress.getPortSpecifierCase()) {
+          case NAMED_PORT:
+            return address + ":" + socketAddress.getNamedPort();
+          case PORT_VALUE:
+            return address + ":" + socketAddress.getPortValue();
+          default:
+            return address;
+        }
+      }
+      return null;
+    }
+
+    static Listener fromEnvoyProtoListener(io.envoyproxy.envoy.api.v2.Listener proto) {
+      List<FilterChain> filterChains = new ArrayList<>(proto.getFilterChainsCount());
+      for (io.envoyproxy.envoy.api.v2.listener.FilterChain filterChain :
+          proto.getFilterChainsList()) {
+        filterChains.add(FilterChain.fromEnvoyProtoFilterChain(filterChain));
+      }
+      return new Listener(
+          proto.getName(),
+          convertEnvoyAddressToString(proto.getAddress()),
+          filterChains);
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public String getAddress() {
+      return address;
+    }
+
+    public List<FilterChain> getFilterChains() {
+      return filterChains;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Listener listener = (Listener) o;
+      return java.util.Objects.equals(name, listener.name)
+          &&  java.util.Objects.equals(address, listener.address)
+          &&  java.util.Objects.equals(filterChains, listener.filterChains);
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(name, address, filterChains);
+    }
+
+    @Override
+    public String toString() {
+      return "Listener{"
+          + "name='" + name + '\''
+          + ", address='" + address + '\''
+          + ", filterChains=" + filterChains
+          + '}';
+    }
+  }
+}
diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java
index 85d15f5..288ad2f 100644
--- a/xds/src/main/java/io/grpc/xds/XdsClient.java
+++ b/xds/src/main/java/io/grpc/xds/XdsClient.java
@@ -34,6 +34,7 @@
 import io.grpc.xds.EnvoyProtoData.DropOverload;
 import io.grpc.xds.EnvoyProtoData.Locality;
 import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
+import io.grpc.xds.EnvoyServerProtoData.Listener;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -59,21 +60,29 @@
    */
   static final class ConfigUpdate {
     private final String clusterName;
+    private final Listener listener;
 
-    private ConfigUpdate(String clusterName) {
+    private ConfigUpdate(String clusterName, @Nullable Listener listener) {
       this.clusterName = clusterName;
+      this.listener = listener;
     }
 
     String getClusterName() {
       return clusterName;
     }
 
+    @Nullable
+    public Listener getListener() {
+      return listener;
+    }
+
     static Builder newBuilder() {
       return new Builder();
     }
 
     static final class Builder {
       private String clusterName;
+      @Nullable private Listener listener;
 
       // Use ConfigUpdate.newBuilder().
       private Builder() {
@@ -84,9 +93,14 @@
         return this;
       }
 
+      Builder setListener(Listener listener) {
+        this.listener = listener;
+        return this;
+      }
+
       ConfigUpdate build() {
         Preconditions.checkState(clusterName != null, "clusterName is not set");
-        return new ConfigUpdate(clusterName);
+        return new ConfigUpdate(clusterName, listener);
       }
     }
   }
diff --git a/xds/src/test/java/io/grpc/xds/EnvoyServerProtoDataTest.java b/xds/src/test/java/io/grpc/xds/EnvoyServerProtoDataTest.java
new file mode 100644
index 0000000..a9e7647
--- /dev/null
+++ b/xds/src/test/java/io/grpc/xds/EnvoyServerProtoDataTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.xds;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.UInt32Value;
+import io.envoyproxy.envoy.api.v2.auth.CommonTlsContext;
+import io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext;
+import io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig;
+import io.envoyproxy.envoy.api.v2.core.CidrRange;
+import io.envoyproxy.envoy.api.v2.core.SocketAddress;
+import io.envoyproxy.envoy.api.v2.listener.Filter;
+import io.envoyproxy.envoy.api.v2.listener.FilterChain;
+import io.envoyproxy.envoy.api.v2.listener.FilterChainMatch;
+import io.grpc.xds.EnvoyServerProtoData.Listener;
+import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link EnvoyServerProtoData}.
+ */
+@RunWith(JUnit4.class)
+public class EnvoyServerProtoDataTest {
+
+  @Test
+  public void listener_convertFromListenerProto() {
+    io.envoyproxy.envoy.api.v2.core.Address address =
+        io.envoyproxy.envoy.api.v2.core.Address.newBuilder()
+            .setSocketAddress(SocketAddress.newBuilder()
+                .setPortValue(8000)
+                .setAddress("10.2.1.34")
+                .build())
+            .build();
+    io.envoyproxy.envoy.api.v2.Listener listener =
+        io.envoyproxy.envoy.api.v2.Listener.newBuilder()
+            .setName("8000")
+            .setAddress(address)
+            .addFilterChains(createOutFilter())
+            .addFilterChains(createInFilter())
+            .build();
+
+    Listener xdsListener = Listener.fromEnvoyProtoListener(listener);
+    assertThat(xdsListener.getName()).isEqualTo("8000");
+    assertThat(xdsListener.getAddress()).isEqualTo("10.2.1.34:8000");
+    List<EnvoyServerProtoData.FilterChain> filterChains = xdsListener.getFilterChains();
+    assertThat(filterChains).isNotNull();
+    assertThat(filterChains.size()).isEqualTo(2);
+    EnvoyServerProtoData.FilterChain outFilter = filterChains.get(0);
+    assertThat(outFilter).isNotNull();
+    EnvoyServerProtoData.FilterChainMatch outFilterChainMatch = outFilter.getFilterChainMatch();
+    assertThat(outFilterChainMatch).isNotNull();
+    assertThat(outFilterChainMatch.getDestinationPort()).isEqualTo(8000);
+    assertThat(outFilterChainMatch.getApplicationProtocols()).isEmpty();
+    assertThat(outFilterChainMatch.getPrefixRanges()).isEmpty();
+    assertThat(outFilter.getDownstreamTlsContext())
+        .isEqualTo(DownstreamTlsContext.getDefaultInstance());
+
+    EnvoyServerProtoData.FilterChain inFilter = filterChains.get(1);
+    assertThat(inFilter).isNotNull();
+    EnvoyServerProtoData.FilterChainMatch inFilterChainMatch = inFilter.getFilterChainMatch();
+    assertThat(inFilterChainMatch).isNotNull();
+    assertThat(inFilterChainMatch.getDestinationPort()).isEqualTo(8000);
+    assertThat(inFilterChainMatch.getApplicationProtocols()).containsExactly("managed-mtls");
+    assertThat(inFilterChainMatch.getPrefixRanges()).containsExactly(
+        new EnvoyServerProtoData.CidrRange("10.20.0.15", 32));
+    DownstreamTlsContext inFilterTlsContext = inFilter.getDownstreamTlsContext();
+    assertThat(inFilterTlsContext).isNotNull();
+    CommonTlsContext commonTlsContext = inFilterTlsContext.getCommonTlsContext();
+    assertThat(commonTlsContext).isNotNull();
+    List<SdsSecretConfig> tlsCertSdsConfigs = commonTlsContext
+        .getTlsCertificateSdsSecretConfigsList();
+    assertThat(tlsCertSdsConfigs).isNotNull();
+    assertThat(tlsCertSdsConfigs).hasSize(1);
+    assertThat(tlsCertSdsConfigs.get(0).getName()).isEqualTo("google-sds-config-default");
+  }
+
+  private static FilterChain createOutFilter() {
+    FilterChain filterChain =
+        FilterChain.newBuilder()
+            .setFilterChainMatch(
+                FilterChainMatch.newBuilder()
+                    .setDestinationPort(UInt32Value.newBuilder().setValue(8000).build())
+                    .build())
+            .addFilters(Filter.newBuilder()
+                .setName("envoy.http_connection_manager")
+                .build())
+            .build();
+    return filterChain;
+  }
+
+  private static FilterChain createInFilter() {
+    FilterChain filterChain =
+        FilterChain.newBuilder()
+            .setFilterChainMatch(
+                FilterChainMatch.newBuilder()
+                    .setDestinationPort(UInt32Value.newBuilder().setValue(8000)
+                        .build())
+                    .addPrefixRanges(CidrRange.newBuilder()
+                        .setAddressPrefix("10.20.0.15")
+                        .setPrefixLen(UInt32Value.newBuilder().setValue(32)
+                            .build()).build())
+                    .addApplicationProtocols("managed-mtls")
+                    .build())
+            .setTlsContext(CommonTlsContextTestsUtil.buildTestDownstreamTlsContext())
+            .addFilters(Filter.newBuilder()
+                .setName("envoy.http_connection_manager")
+                .setTypedConfig(Any.newBuilder()
+                    .setTypeUrl(
+                        "type.googleapis.com/envoy.config.filter.network.http_connection_manager"
+                            + ".v2.HttpConnectionManager"))
+                .build())
+            .build();
+    return filterChain;
+  }
+}
diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
index b3d95aa..0fc7511 100644
--- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
+++ b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java
@@ -20,9 +20,11 @@
 import io.envoyproxy.envoy.api.v2.auth.CertificateValidationContext;
 import io.envoyproxy.envoy.api.v2.auth.CommonTlsContext;
 import io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.CombinedCertificateValidationContext;
+import io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext;
 import io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig;
 import io.envoyproxy.envoy.api.v2.auth.TlsCertificate;
 import io.envoyproxy.envoy.api.v2.core.DataSource;
+import java.util.Arrays;
 
 /** Utility class for client and server ssl provider tests. */
 public class CommonTlsContextTestsUtil {
@@ -57,27 +59,6 @@
     return builder.build();
   }
 
-  static CommonTlsContext buildCommonTlsContextFromSdsConfigsForAll(
-      String certName,
-      String certTargetUri,
-      String validationContextName,
-      String validationContextTargetUri,
-      String channelType) {
-
-    CommonTlsContext.Builder builder = CommonTlsContext.newBuilder();
-
-    SdsSecretConfig sdsSecretConfig = buildSdsSecretConfig(certName, certTargetUri, channelType);
-    if (sdsSecretConfig != null) {
-      builder.addTlsCertificateSdsSecretConfigs(sdsSecretConfig);
-    }
-    sdsSecretConfig =
-        buildSdsSecretConfig(validationContextName, validationContextTargetUri, channelType);
-    if (sdsSecretConfig != null) {
-      builder.setValidationContextSdsSecretConfig(sdsSecretConfig);
-    }
-    return builder.build();
-  }
-
   static CommonTlsContext buildCommonTlsContextFromSdsConfigForTlsCertificate(
       String name, String targetUri, String trustCa) {
 
@@ -134,4 +115,27 @@
     }
     return builder.build();
   }
+
+  /**
+   * Helper method to build DownstreamTlsContext for multiple test classes.
+   */
+  static DownstreamTlsContext buildDownstreamTlsContext(CommonTlsContext commonTlsContext) {
+    DownstreamTlsContext downstreamTlsContext =
+        DownstreamTlsContext.newBuilder().setCommonTlsContext(commonTlsContext).build();
+    return downstreamTlsContext;
+  }
+
+  /** Helper method for creating DownstreamTlsContext values for tests. */
+  public static DownstreamTlsContext buildTestDownstreamTlsContext() {
+    return buildDownstreamTlsContext(
+        buildCommonTlsContextWithAdditionalValues(
+            "google-sds-config-default",
+            "unix:/var/run/sds/uds_path",
+            "ROOTCA",
+            "unix:/var/run/sds/uds_path",
+            Arrays.asList("spiffe://grpc-sds-testing.svc.id.goog/ns/default/sa/bob"),
+            Arrays.asList("managed-tls"),
+            null
+        ));
+  }
 }
diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java
index 186e155..1a31b6c 100644
--- a/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java
@@ -80,7 +80,7 @@
 
     return server
         ? SdsSslContextProvider.getProviderForServer(
-            SecretVolumeSslContextProviderTest.buildDownstreamTlsContext(commonTlsContext),
+            CommonTlsContextTestsUtil.buildDownstreamTlsContext(commonTlsContext),
             node,
             MoreExecutors.directExecutor(),
             MoreExecutors.directExecutor())
diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java
index 572ba58..6477188 100644
--- a/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java
@@ -277,7 +277,8 @@
     TlsCertificate tlsCert = TlsCertificate.getDefaultInstance();
     try {
       SecretVolumeSslContextProvider.getProviderForServer(
-          buildDownstreamTlsContext(getCommonTlsContext(tlsCert, /* certContext= */ null)));
+          CommonTlsContextTestsUtil
+              .buildDownstreamTlsContext(getCommonTlsContext(tlsCert, /* certContext= */ null)));
       Assert.fail("no exception thrown");
     } catch (IllegalArgumentException expected) {
       assertThat(expected).hasMessageThat().isEqualTo("filename expected");
@@ -297,7 +298,8 @@
             .build();
     try {
       SecretVolumeSslContextProvider.getProviderForServer(
-          buildDownstreamTlsContext(getCommonTlsContext(tlsCert, certContext)));
+          CommonTlsContextTestsUtil
+              .buildDownstreamTlsContext(getCommonTlsContext(tlsCert, certContext)));
       Assert.fail("no exception thrown");
     } catch (IllegalArgumentException expected) {
       assertThat(expected.getMessage()).isEqualTo("filename expected");
@@ -419,7 +421,7 @@
    */
   static DownstreamTlsContext buildDownstreamTlsContextFromFilenames(
       String privateKey, String certChain, String trustCa) {
-    return buildDownstreamTlsContext(
+    return CommonTlsContextTestsUtil.buildDownstreamTlsContext(
         buildCommonTlsContextFromFilenames(privateKey, certChain, trustCa));
   }
 
@@ -465,15 +467,6 @@
   }
 
   /**
-   * Helper method to build DownstreamTlsContext for above tests. Called from other classes as well.
-   */
-  static DownstreamTlsContext buildDownstreamTlsContext(CommonTlsContext commonTlsContext) {
-    DownstreamTlsContext downstreamTlsContext =
-        DownstreamTlsContext.newBuilder().setCommonTlsContext(commonTlsContext).build();
-    return downstreamTlsContext;
-  }
-
-  /**
    * Helper method to build UpstreamTlsContext for above tests. Called from other classes as well.
    */
   static UpstreamTlsContext buildUpstreamTlsContext(CommonTlsContext commonTlsContext) {
diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java
index 283c60d..b61c629 100644
--- a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java
@@ -53,7 +53,7 @@
         CommonTlsContextTestsUtil.buildCommonTlsContextFromSdsConfigForTlsCertificate(
             "name", "unix:/tmp/sds/path", CA_PEM_FILE);
     DownstreamTlsContext downstreamTlsContext =
-        SecretVolumeSslContextProviderTest.buildDownstreamTlsContext(commonTlsContext);
+        CommonTlsContextTestsUtil.buildDownstreamTlsContext(commonTlsContext);
 
     try {
       SslContextProvider<DownstreamTlsContext> unused =
@@ -72,7 +72,7 @@
         CommonTlsContextTestsUtil.buildCommonTlsContextFromSdsConfigForValidationContext(
             "name", "unix:/tmp/sds/path", SERVER_KEY_FILE, SERVER_PEM_FILE);
     DownstreamTlsContext downstreamTlsContext =
-        SecretVolumeSslContextProviderTest.buildDownstreamTlsContext(commonTlsContext);
+        CommonTlsContextTestsUtil.buildDownstreamTlsContext(commonTlsContext);
 
     try {
       SslContextProvider<DownstreamTlsContext> unused =