* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
+
package org.opendaylight.netvirt.aclservice.utils;
import java.util.ArrayList;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.packet.fields.rev160218.acl.transport.header.fields.DestinationPortRange;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.packet.fields.rev160218.acl.transport.header.fields.SourcePortRange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class AclServiceOFFlowBuilder {
- private AclServiceOFFlowBuilder() {
- }
+ private static final Logger LOG =
+ LoggerFactory.getLogger(AclServiceOFFlowBuilder.class);
/**
* Converts IP matches into flows.
- * @param matches the matches
+ * @param matches
+ * the matches
* @return the map containing the flows and the respective flow id
*/
- public static Map<String,List<MatchInfoBase>> programIpFlow(Matches matches) {
- new HashMap<>();
- AceIp acl = (AceIp)matches.getAceType();
- if (acl.getProtocol() == NwConstants.IP_PROT_TCP) {
- return programTcpFlow(acl);
- } else if (acl.getProtocol() == NwConstants.IP_PROT_UDP) {
- return programUdpFlow(acl);
- } else if (acl.getProtocol() == NwConstants.IP_PROT_ICMP) {
- return programIcmpFlow(acl);
- } else if (acl.getProtocol() != -1) {
- return programOtherProtocolFlow(acl);
+ public static Map<String, List<MatchInfoBase>> programIpFlow(Matches matches) {
+ if (matches != null) {
+ AceIp acl = (AceIp) matches.getAceType();
+ if (acl.getProtocol() == NwConstants.IP_PROT_TCP) {
+ return programTcpFlow(acl);
+ } else if (acl.getProtocol() == NwConstants.IP_PROT_UDP) {
+ return programUdpFlow(acl);
+ } else if (acl.getProtocol() == NwConstants.IP_PROT_ICMP) {
+ return programIcmpFlow(acl);
+ } else if (acl.getProtocol() != -1) {
+ return programOtherProtocolFlow(acl);
+ }
}
return null;
-
}
/** Converts generic protocol matches to flows.
*
*/
public static Map<Integer,Integer> getLayer4MaskForRange(int portMin, int portMax) {
- int [] offset = {32768,16384,8192,4096,2048,1024,512,256,128,64,32,16,8,4,2,1};
- int[] mask = {0x8000,0xC000,0xE000,0xF000,0xF800,0xFC00,0xFE00,0xFF00,
- 0xFF80,0xFFC0,0xFFE0,0xFFF0,0xFFF8,0xFFFC,0xFFFE,0xFFFF};
+ final int[] offset = { 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1 };
+ final int[] mask = { 0x8000, 0xC000, 0xE000, 0xF000, 0xF800, 0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0,
+ 0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF };
int noOfPorts = portMax - portMin + 1;
Map<Integer,Integer> portMap = new HashMap<>();
if (noOfPorts == 1) {
portMap.put(portMin, mask[15]);
return portMap;
}
+ if (noOfPorts < 0) { // TODO: replace with infrautils.counter in case of high repetitive usage
+ LOG.warn("Cannot convert port range into a set of masked port ranges - Illegal port range {}-{}", portMin,
+ portMax);
+ return portMap;
+ }
String binaryNoOfPorts = Integer.toBinaryString(noOfPorts);
+ if (binaryNoOfPorts.length() > 16) { // TODO: replace with infrautils.counter in case of high repetitive usage
+ LOG.warn("Cannot convert port range into a set of masked port ranges - Illegal port range {}-{}", portMin,
+ portMax);
+ return portMap;
+ }
int medianOffset = 16 - binaryNoOfPorts.length();
int medianLength = offset[medianOffset];
int median = 0;
--- /dev/null
+/*
+ * Copyright (c) 2016 Hewlett Packard Enterprise, Co. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netvirt.aclservice.utils;
+
+import static com.google.common.collect.Iterables.filter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.common.collect.Iterables;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.genius.mdsalutil.MatchFieldType;
+import org.opendaylight.genius.mdsalutil.MatchInfo;
+import org.opendaylight.genius.mdsalutil.MatchInfoBase;
+import org.opendaylight.genius.mdsalutil.NwConstants;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.Matches;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIpBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv4Builder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
+
+
+public class AclServiceOFFlowBuilderTest {
+
+ @Test
+ public void testProgramIpFlow_NullMatches() {
+ Matches matches = null;
+ Map<String, List<MatchInfoBase>> flowMap = AclServiceOFFlowBuilder.programIpFlow(matches);
+ assertNull(flowMap);
+ }
+
+ @Test
+ public void testprogramOtherProtocolFlow() {
+ AceIpBuilder builder = AclServiceTestUtils.prepareAceIpBuilder("10.1.1.1/24", "20.1.1.1/24", null, null,
+ (short) 1);
+ Map<String, List<MatchInfoBase>> flowMatchesMap =
+ AclServiceOFFlowBuilder.programOtherProtocolFlow(builder.build());
+ List<MatchInfoBase> flowMatches = flowMatchesMap.get("OTHER_PROTO" + "1");
+ AclServiceTestUtils.verifyGeneralFlows(flowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+ }
+
+ @Test
+ public void testprogramIcmpFlow() {
+ AceIpBuilder builder = AclServiceTestUtils.prepareAceIpBuilder("10.1.1.1/24", "20.1.1.1/24", "1024", "2048",
+ (short) 1);
+ Map<String, List<MatchInfoBase>> flowMatchesMap = AclServiceOFFlowBuilder.programIcmpFlow(builder.build());
+ List<MatchInfoBase> flowMatches = flowMatchesMap.entrySet().iterator().next().getValue();
+
+ AclServiceTestUtils.verifyGeneralFlows(flowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+
+ Iterable<MatchInfoBase> icmpv4Matches = filter(flowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(MatchFieldType.icmp_v4)));
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.get(icmpv4Matches, 0), "1024", "2048");
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.get(icmpv4Matches, 1), "4096", "8192");
+ }
+
+ @Test
+ public void testprogramTcpFlow_NoSrcDstPortRange() {
+ AceIpBuilder builder = AclServiceTestUtils.prepareAceIpBuilder("10.1.1.1/24", "20.1.1.1/24", null, null,
+ (short) 1);
+
+ Map<String, List<MatchInfoBase>> flowMatchesMap = AclServiceOFFlowBuilder.programTcpFlow(builder.build());
+ List<MatchInfoBase> flowMatches = flowMatchesMap.get("TCP_SOURCE_ALL_");
+
+ AclServiceTestUtils.verifyGeneralFlows(flowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.tcp_src);
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.tcp_dst);
+ }
+
+ @Test
+ public void testprogramTcpFlow_WithSrcDstPortRange() {
+ AceIpBuilder builder = AclServiceTestUtils.prepareAceIpBuilder("10.1.1.1/24", "20.1.1.1/24", "1024", "1024",
+ (short) 1);
+
+ Map<String, List<MatchInfoBase>> flowMatchesMap = AclServiceOFFlowBuilder.programTcpFlow(builder.build());
+
+ List<MatchInfoBase> srcFlowMatches = new ArrayList<MatchInfoBase>();
+ List<MatchInfoBase> dstFlowMatches = new ArrayList<MatchInfoBase>();
+
+ for (String flowId : flowMatchesMap.keySet()) {
+ if (flowId.startsWith("TCP_SOURCE_")) {
+ srcFlowMatches.addAll(flowMatchesMap.get(flowId));
+ }
+ if (flowId.startsWith("TCP_DESTINATION_")) {
+ dstFlowMatches.addAll(flowMatchesMap.get(flowId));
+ }
+ }
+
+ AclServiceTestUtils.verifyGeneralFlows(srcFlowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+ Iterable<MatchInfoBase> tcpSrcMatches = filter(srcFlowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(MatchFieldType.tcp_src)));
+
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.getFirst(tcpSrcMatches, null), "1024");
+
+ AclServiceTestUtils.verifyGeneralFlows(dstFlowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+ Iterable<MatchInfoBase> tcpDstMatches = filter(dstFlowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(MatchFieldType.tcp_dst)));
+
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.getFirst(tcpDstMatches, null), "1024");
+ }
+
+ @Test
+ public void testProgramUdpFlow_NoSrcDstPortRange() {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ v4builder.setSourceIpv4Network(new Ipv4Prefix("10.1.1.1/24"));
+ v4builder.setDestinationIpv4Network(new Ipv4Prefix("20.1.1.1/24"));
+ builder.setAceIpVersion(v4builder.build());
+ builder.setSourcePortRange(null);
+ builder.setDestinationPortRange(null);
+ short protocol = 1;
+ builder.setProtocol(protocol);
+
+ Map<String, List<MatchInfoBase>> flowMatchesMap = AclServiceOFFlowBuilder.programUdpFlow(builder.build());
+
+ List<MatchInfoBase> flowMatches = flowMatchesMap.get("UDP_SOURCE_ALL_");
+
+ AclServiceTestUtils.verifyGeneralFlows(flowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.udp_src);
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.udp_dst);
+ }
+
+ @Test
+ public void testprogramUdpFlow_WithSrcDstPortRange() {
+ AceIpBuilder builder = AclServiceTestUtils.prepareAceIpBuilder("10.1.1.1/24", "20.1.1.1/24", "1024", "1024",
+ (short) 1);
+
+ Map<String, List<MatchInfoBase>> flowMatchesMap = AclServiceOFFlowBuilder.programUdpFlow(builder.build());
+ List<MatchInfoBase> srcFlowMatches = new ArrayList<MatchInfoBase>();
+ List<MatchInfoBase> dstFlowMatches = new ArrayList<MatchInfoBase>();
+
+ for (String flowId : flowMatchesMap.keySet()) {
+ if (flowId.startsWith("UDP_SOURCE_")) {
+ srcFlowMatches.addAll(flowMatchesMap.get(flowId));
+ }
+ if (flowId.startsWith("UDP_DESTINATION_")) {
+ dstFlowMatches.addAll(flowMatchesMap.get(flowId));
+ }
+ }
+
+ AclServiceTestUtils.verifyGeneralFlows(srcFlowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+
+ Iterable<MatchInfoBase> udpSrcMatches = filter(srcFlowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(MatchFieldType.udp_src)));
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.getFirst(udpSrcMatches, null), "1024");
+
+ AclServiceTestUtils.verifyGeneralFlows(dstFlowMatches, "1", "10.1.1.1", "20.1.1.1", "24");
+
+ Iterable<MatchInfoBase> udpDstMatches = filter(dstFlowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(MatchFieldType.udp_dst)));
+ AclServiceTestUtils.verifyMatchValues((MatchInfo) Iterables.getFirst(udpDstMatches, null), "1024");
+ }
+
+ @Test
+ public void testaddDstIpMatches_v4() {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ v4builder.setDestinationIpv4Network(new Ipv4Prefix("10.1.1.1/24"));
+ builder.setAceIpVersion(v4builder.build());
+
+ List<MatchInfoBase> flowMatches = AclServiceOFFlowBuilder.addDstIpMatches(builder.build());
+
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.eth_type,
+ Integer.toString(NwConstants.ETHTYPE_IPV4));
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.ipv4_destination, "10.1.1.1", "24");
+ }
+
+ @Test
+ public void testaddDstIpMatches_v4NoDstNetwork() {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ v4builder.setDestinationIpv4Network(null);
+ builder.setAceIpVersion(v4builder.build());
+
+ List<MatchInfoBase> flowMatches = AclServiceOFFlowBuilder.addDstIpMatches(builder.build());
+
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.eth_type,
+ Integer.toString(NwConstants.ETHTYPE_IPV4));
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.ipv4_destination);
+ }
+
+ @Test
+ public void testaddSrcIpMatches_v4() {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ v4builder.setSourceIpv4Network(new Ipv4Prefix("10.1.1.1/24"));
+ builder.setAceIpVersion(v4builder.build());
+
+ List<MatchInfoBase> flowMatches = AclServiceOFFlowBuilder.addSrcIpMatches(builder.build());
+
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.eth_type,
+ Integer.toString(NwConstants.ETHTYPE_IPV4));
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.ipv4_source, "10.1.1.1", "24");
+ }
+
+ @Test
+ public void testaddSrcIpMatches_v4NoSrcNetwork() {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ v4builder.setSourceIpv4Network(null);
+ builder.setAceIpVersion(v4builder.build());
+
+ List<MatchInfoBase> flowMatches = AclServiceOFFlowBuilder.addSrcIpMatches(builder.build());
+ AclServiceTestUtils.verifyMatchInfo(flowMatches, MatchFieldType.eth_type,
+ Integer.toString(NwConstants.ETHTYPE_IPV4));
+ AclServiceTestUtils.verifyMatchFieldTypeDontExist(flowMatches, MatchFieldType.ipv4_source);
+ }
+
+ @Test
+ public void testgetLayer4MaskForRange_SinglePort() {
+ Map<Integer, Integer> layer4MaskForRange = AclServiceOFFlowBuilder.getLayer4MaskForRange(1111, 1111);
+ assertEquals("port L4 mask missing", 1, layer4MaskForRange.size());
+ }
+
+ @Test
+ public void testgetLayer4MaskForRange_MultiplePorts() {
+ Map<Integer, Integer> layer4MaskForRange = AclServiceOFFlowBuilder.getLayer4MaskForRange(1024, 2048);
+ assertEquals("port L4 mask missing", 2, layer4MaskForRange.size());
+ }
+
+ @Test
+ public void testgetLayer4MaskForRange_IllegalPortRange_ExceedMin() {
+ Map<Integer, Integer> layer4MaskForRange = AclServiceOFFlowBuilder.getLayer4MaskForRange(0, 1);
+
+ assertEquals("port L4 mask missing", 1, layer4MaskForRange.size());
+ }
+
+ @Test
+ public void testgetLayer4MaskForRange_IllegalPortRange_ExceedMax() {
+ Map<Integer, Integer> layer4MaskForRange = AclServiceOFFlowBuilder.getLayer4MaskForRange(1, 65536);
+ assertEquals("Illegal ports range", 0, layer4MaskForRange.size());
+ }
+
+ @Test
+ public void testgetLayer4MaskForRange_IllegalPortRange_MinGreaterThanMax() {
+ Map<Integer, Integer> layer4MaskForRange = AclServiceOFFlowBuilder.getLayer4MaskForRange(8192, 4096);
+ assertEquals("Illegal ports range", 0, layer4MaskForRange.size());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Hewlett Packard Enterprise, Co. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netvirt.aclservice.utils;
+
+import static com.google.common.collect.Iterables.filter;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Iterables;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.opendaylight.genius.mdsalutil.MatchFieldType;
+import org.opendaylight.genius.mdsalutil.MatchInfo;
+import org.opendaylight.genius.mdsalutil.MatchInfoBase;
+import org.opendaylight.genius.mdsalutil.NwConstants;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIpBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv4Builder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.packet.fields.rev160218.acl.transport.header.fields.DestinationPortRangeBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.packet.fields.rev160218.acl.transport.header.fields.SourcePortRangeBuilder;
+
+
+public class AclServiceTestUtils {
+
+ public static void verifyGeneralFlows(List<MatchInfoBase> srcFlowMatches, String protocol, String srcIpv4Net,
+ String dstIpv4Net, String mask) {
+ verifyMatchInfo(srcFlowMatches, MatchFieldType.eth_type, Integer.toString(NwConstants.ETHTYPE_IPV4));
+ verifyMatchInfo(srcFlowMatches, MatchFieldType.ip_proto, protocol);
+ verifyMatchInfo(srcFlowMatches, MatchFieldType.ipv4_source, srcIpv4Net, mask);
+ verifyMatchInfo(srcFlowMatches, MatchFieldType.ipv4_destination, dstIpv4Net, mask);
+ }
+
+ public static AceIpBuilder prepareAceIpBuilder(String srcIpv4Net, String dstIpv4Net, String lowerPort,
+ String upperPort, short protocol) {
+ AceIpBuilder builder = new AceIpBuilder();
+ AceIpv4Builder v4builder = new AceIpv4Builder();
+ if (srcIpv4Net != null) {
+ v4builder.setSourceIpv4Network(new Ipv4Prefix(srcIpv4Net));
+ } else {
+ v4builder.setSourceIpv4Network(null);
+ }
+
+ if (dstIpv4Net != null) {
+ v4builder.setDestinationIpv4Network(new Ipv4Prefix(dstIpv4Net));
+ } else {
+ v4builder.setDestinationIpv4Network(null);
+ }
+ builder.setAceIpVersion(v4builder.build());
+ if (lowerPort != null && upperPort != null) {
+ SourcePortRangeBuilder srcPortBuilder = new SourcePortRangeBuilder();
+ srcPortBuilder.setLowerPort(PortNumber.getDefaultInstance(lowerPort));
+ srcPortBuilder.setUpperPort(PortNumber.getDefaultInstance(upperPort));
+ builder.setSourcePortRange(srcPortBuilder.build());
+ DestinationPortRangeBuilder dstPortBuilder = new DestinationPortRangeBuilder();
+ dstPortBuilder.setLowerPort(PortNumber.getDefaultInstance(lowerPort));
+ dstPortBuilder.setUpperPort(PortNumber.getDefaultInstance(upperPort));
+ builder.setDestinationPortRange(dstPortBuilder.build());
+ }
+ builder.setProtocol(protocol);
+ return builder;
+ }
+
+ public static void verifyMatchInfo(List<MatchInfoBase> flowMatches, MatchFieldType matchType, String... params) {
+ Iterable<MatchInfoBase> matches = filter(flowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(matchType)));
+ for (MatchInfoBase baseMatch : matches) {
+ verifyMatchValues((MatchInfo) baseMatch, params);
+ }
+ }
+
+ public static void verifyMatchValues(MatchInfo match, String... params) {
+ switch (match.getMatchField()) {
+ case tcp_src:
+ case tcp_dst:
+ case ip_proto:
+ case udp_src:
+ case udp_dst:
+ case eth_type:
+ long[] values = Arrays.stream(params).mapToLong(l -> Long.parseLong(l)).toArray();
+ Assert.assertArrayEquals(values, match.getMatchValues());
+ break;
+ case ipv4_source:
+ case ipv4_destination:
+ Assert.assertArrayEquals(params, match.getStringMatchValues());
+ break;
+ default:
+ assertTrue("match type is not supported", true);
+ break;
+ }
+ }
+
+ public static void verifyMatchFieldTypeDontExist(List<MatchInfoBase> flowMatches, MatchFieldType matchType) {
+ Iterable<MatchInfoBase> matches = filter(flowMatches,
+ (item -> ((MatchInfo) item).getMatchField().equals(matchType)));
+ Assert.assertTrue("unexpected match type " + matchType.name(), Iterables.isEmpty(matches));
+ }
+}