xds: fix filterChainMatch for multiple matchers, 0-length prefixLength and empty prefixRange list (#7985)

diff --git a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java
index 17ba379..24d6f21 100644
--- a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java
+++ b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java
@@ -216,9 +216,10 @@
     filterChains = filterOnIpAddress(filterChains, remoteInetAddr.getAddress(), false);
     filterChains = filterOnSourcePort(filterChains, remoteInetAddr.getPort());
 
-    // if we get more than 1, we ignore filterChains and use the defaultFilerChain
-    // although spec not clear for that case
-    if (filterChains.size() == 1) {
+    if (filterChains.size() > 1) {
+      // close the connection
+      throw new IllegalStateException("Found 2 matching filter-chains");
+    } else if (filterChains.size() == 1) {
       return filterChains.get(0).getDownstreamTlsContext();
     }
     return curListener.getDefaultFilterChain().getDownstreamTlsContext();
@@ -307,10 +308,10 @@
               ? filterChainMatch.getPrefixRanges()
               : filterChainMatch.getSourcePrefixRanges();
       indexOfMatchingPrefixRange = -1;
-      if (cidrRanges.isEmpty()) { // if there is no CidrRange assume there is perfect match
-        matchingPrefixLength = isIPv6 ? 128 : 32;
-      } else {
+      if (cidrRanges.isEmpty()) { // if there is no CidrRange assume 0-length match
         matchingPrefixLength = 0;
+      } else {
+        matchingPrefixLength = -1;
         int index = 0;
         for (CidrRange cidrRange : cidrRanges) {
           InetAddress cidrAddr = cidrRange.getAddressPrefix();
@@ -357,7 +358,7 @@
     for (FilterChain filterChain : filterChains) {
       QueueElement element = new QueueElement(filterChain, address, forDestination);
 
-      if (element.matchingPrefixLength > 0) {
+      if (element.matchingPrefixLength >= 0) {
         heap.add(element);
       }
     }
diff --git a/xds/src/test/java/io/grpc/xds/FilterChainMatchTest.java b/xds/src/test/java/io/grpc/xds/FilterChainMatchTest.java
index b92b1b8..e13064c 100644
--- a/xds/src/test/java/io/grpc/xds/FilterChainMatchTest.java
+++ b/xds/src/test/java/io/grpc/xds/FilterChainMatchTest.java
@@ -17,6 +17,7 @@
 package io.grpc.xds;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.when;
 
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -218,6 +219,37 @@
   }
 
   @Test
+  public void dest0LengthPrefixRange()
+          throws UnknownHostException, InvalidProtocolBufferException {
+    setupChannel(LOCAL_IP, REMOTE_IP, 15000);
+    DownstreamTlsContext tlsContext0Length =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1");
+    // 10.2.2.0/24 doesn't match LOCAL_IP
+    EnvoyServerProtoData.FilterChainMatch filterChainMatch0Length =
+        new EnvoyServerProtoData.FilterChainMatch(
+            0,
+            Arrays.asList(new EnvoyServerProtoData.CidrRange("10.2.2.0", 0)),
+            Arrays.<String>asList(),
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            EnvoyServerProtoData.ConnectionSourceType.ANY,
+            Arrays.<Integer>asList());
+    EnvoyServerProtoData.FilterChain filterChain0Length =
+        new EnvoyServerProtoData.FilterChain(filterChainMatch0Length, tlsContext0Length);
+    DownstreamTlsContext tlsContextForDefaultFilterChain =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2");
+    EnvoyServerProtoData.FilterChain defaultFilterChain =
+        new EnvoyServerProtoData.FilterChain(null, tlsContextForDefaultFilterChain);
+    EnvoyServerProtoData.Listener listener =
+        new EnvoyServerProtoData.Listener(
+            "listener1", LOCAL_IP, Arrays.asList(filterChain0Length), defaultFilterChain);
+    XdsClient.LdsUpdate listenerUpdate = new XdsClient.LdsUpdate(listener);
+    registeredWatcher.onChanged(listenerUpdate);
+    DownstreamTlsContext tlsContext1 =
+        xdsClientWrapperForServerSds.getDownstreamTlsContext(channel);
+    assertThat(tlsContext1).isSameInstanceAs(tlsContext0Length);
+  }
+
+  @Test
   public void destPrefixRange_moreSpecificWins()
       throws UnknownHostException, InvalidProtocolBufferException {
     setupChannel(LOCAL_IP, REMOTE_IP, 15000);
@@ -262,6 +294,50 @@
   }
 
   @Test
+  public void destPrefixRange_emptyListLessSpecific()
+          throws UnknownHostException, InvalidProtocolBufferException {
+    setupChannel(LOCAL_IP, REMOTE_IP, 15000);
+    DownstreamTlsContext tlsContextLessSpecific =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1");
+    EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific =
+        new EnvoyServerProtoData.FilterChainMatch(
+            0,
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            Arrays.<String>asList(),
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            EnvoyServerProtoData.ConnectionSourceType.ANY,
+            Arrays.<Integer>asList());
+    EnvoyServerProtoData.FilterChain filterChainLessSpecific =
+        new EnvoyServerProtoData.FilterChain(filterChainMatchLessSpecific, tlsContextLessSpecific);
+
+    DownstreamTlsContext tlsContextMoreSpecific =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2");
+    EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecific =
+        new EnvoyServerProtoData.FilterChainMatch(
+            0,
+            Arrays.asList(new EnvoyServerProtoData.CidrRange("8.0.0.0", 5)),
+            Arrays.<String>asList(),
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            EnvoyServerProtoData.ConnectionSourceType.ANY,
+            Arrays.<Integer>asList());
+    EnvoyServerProtoData.FilterChain filterChainMoreSpecific =
+        new EnvoyServerProtoData.FilterChain(filterChainMatchMoreSpecific, tlsContextMoreSpecific);
+    EnvoyServerProtoData.FilterChain defaultFilterChain =
+        new EnvoyServerProtoData.FilterChain(null, null);
+    EnvoyServerProtoData.Listener listener =
+        new EnvoyServerProtoData.Listener(
+            "listener1",
+            LOCAL_IP,
+            Arrays.asList(filterChainLessSpecific, filterChainMoreSpecific),
+            defaultFilterChain);
+    XdsClient.LdsUpdate listenerUpdate = new XdsClient.LdsUpdate(listener);
+    registeredWatcher.onChanged(listenerUpdate);
+    DownstreamTlsContext tlsContext1 =
+        xdsClientWrapperForServerSds.getDownstreamTlsContext(channel);
+    assertThat(tlsContext1).isSameInstanceAs(tlsContextMoreSpecific);
+  }
+
+  @Test
   public void destPrefixRangeIpv6_moreSpecificWins()
       throws UnknownHostException, InvalidProtocolBufferException {
     setupChannel("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", "2001:DB8::8:800:200C:417A", 15000);
@@ -458,6 +534,52 @@
   }
 
   @Test
+  public void sourcePrefixRange_2Matchers_expectException()
+          throws UnknownHostException, InvalidProtocolBufferException {
+    setupChannel(LOCAL_IP, REMOTE_IP, 15000);
+    DownstreamTlsContext tlsContext1 =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1");
+    EnvoyServerProtoData.FilterChainMatch filterChainMatch1 =
+        new EnvoyServerProtoData.FilterChainMatch(
+            0,
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            Arrays.<String>asList(),
+            Arrays.asList(
+                new EnvoyServerProtoData.CidrRange("10.4.2.0", 24),
+                new EnvoyServerProtoData.CidrRange("192.168.10.2", 32)),
+            EnvoyServerProtoData.ConnectionSourceType.ANY,
+            Arrays.<Integer>asList());
+    EnvoyServerProtoData.FilterChain filterChain1 =
+        new EnvoyServerProtoData.FilterChain(filterChainMatch1, tlsContext1);
+
+    DownstreamTlsContext tlsContext2 =
+        CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2");
+    EnvoyServerProtoData.FilterChainMatch filterChainMatch2 =
+        new EnvoyServerProtoData.FilterChainMatch(
+            0,
+            Arrays.<EnvoyServerProtoData.CidrRange>asList(),
+            Arrays.<String>asList(),
+            Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.2.0", 24)),
+            EnvoyServerProtoData.ConnectionSourceType.ANY,
+            Arrays.<Integer>asList());
+    EnvoyServerProtoData.FilterChain filterChain2 =
+        new EnvoyServerProtoData.FilterChain(filterChainMatch2, tlsContext2);
+    EnvoyServerProtoData.FilterChain defaultFilterChain =
+        new EnvoyServerProtoData.FilterChain(null, null);
+    EnvoyServerProtoData.Listener listener =
+        new EnvoyServerProtoData.Listener(
+            "listener1", LOCAL_IP, Arrays.asList(filterChain1, filterChain2), defaultFilterChain);
+    XdsClient.LdsUpdate listenerUpdate = new XdsClient.LdsUpdate(listener);
+    registeredWatcher.onChanged(listenerUpdate);
+    try {
+      xdsClientWrapperForServerSds.getDownstreamTlsContext(channel);
+      fail("expect exception!");
+    } catch (IllegalStateException ise) {
+      assertThat(ise).hasMessageThat().isEqualTo("Found 2 matching filter-chains");
+    }
+  }
+
+  @Test
   public void sourcePortMatch_exactMatchWinsOverEmptyList()
       throws UnknownHostException, InvalidProtocolBufferException {
     setupChannel(LOCAL_IP, REMOTE_IP, 15000);