http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilter.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilter.java b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilter.java index a7751b4..001b500 100644 --- a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilter.java +++ b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilter.java @@ -21,6 +21,7 @@ package org.apache.metron.pcap.filter.fixed; import com.google.common.base.Joiner; import org.apache.hadoop.conf.Configuration; import org.apache.metron.common.Constants; +import org.apache.metron.common.dsl.MapVariableResolver; import org.apache.metron.common.dsl.VariableResolver; import org.apache.metron.pcap.PacketInfo; import org.apache.metron.pcap.PcapHelper; @@ -28,69 +29,123 @@ import org.apache.metron.pcap.filter.PcapFilter; import org.apache.metron.pcap.filter.PcapFilterConfigurator; import org.apache.metron.pcap.filter.PcapFilters; import org.apache.metron.pcap.filter.PcapFieldResolver; +import org.apache.metron.pcap.pattern.ByteArrayMatchingUtil; +import javax.xml.bind.DatatypeConverter; import java.util.EnumMap; import java.util.Map; +import java.util.concurrent.ExecutionException; public class FixedPcapFilter implements PcapFilter { - public static class Configurator implements PcapFilterConfigurator<EnumMap<Constants.Fields, String>> { + public static class Configurator implements PcapFilterConfigurator<Map<String, String>> { @Override - public void addToConfig(EnumMap<Constants.Fields, String> fields, Configuration conf) { - for (Map.Entry<Constants.Fields, String> kv : fields.entrySet()) { - conf.set(kv.getKey().getName(), kv.getValue()); + public void addToConfig(Map<String, String> fields, Configuration conf) { + for (Map.Entry<String, String> kv : fields.entrySet()) { + conf.set(kv.getKey(), kv.getValue()); } conf.set(PCAP_FILTER_NAME_CONF, PcapFilters.FIXED.name()); } @Override - public String queryToString(EnumMap<Constants.Fields, String> fields) { + public String queryToString(Map<String, String> fields) { return (fields == null ? "" : Joiner.on("_").join(fields.values())); } } + private String packetFilter; private String srcAddr; private Integer srcPort; private String dstAddr; private Integer dstPort; private String protocol; private boolean includesReverseTraffic = false; + private boolean doHeaderFiltering = false; @Override public void configure(Iterable<Map.Entry<String, String>> config) { for (Map.Entry<String, String> kv : config) { if (kv.getKey().equals(Constants.Fields.DST_ADDR.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.dstAddr = kv.getValue(); + doHeaderFiltering = true; } if (kv.getKey().equals(Constants.Fields.SRC_ADDR.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.srcAddr = kv.getValue(); + doHeaderFiltering = true; } if (kv.getKey().equals(Constants.Fields.DST_PORT.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.dstPort = Integer.parseInt(kv.getValue()); + doHeaderFiltering = true; } if (kv.getKey().equals(Constants.Fields.SRC_PORT.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.srcPort = Integer.parseInt(kv.getValue()); + doHeaderFiltering = true; } if (kv.getKey().equals(Constants.Fields.PROTOCOL.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.protocol = kv.getValue(); + doHeaderFiltering = true; } if (kv.getKey().equals(Constants.Fields.INCLUDES_REVERSE_TRAFFIC.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); this.includesReverseTraffic = Boolean.parseBoolean(kv.getValue()); } + if(kv.getKey().equals(PcapHelper.PacketFields.PACKET_FILTER.getName())) { + System.out.println("Processing: " + kv.getKey() + " => " + kv.getValue()); + this.packetFilter = kv.getValue(); + } } } @Override public boolean test(PacketInfo pi) { - VariableResolver resolver = new PcapFieldResolver(packetToFields(pi)); + Map<String, Object> fields = packetToFields(pi); + VariableResolver resolver = new MapVariableResolver(fields); String srcAddrIn = (String) resolver.resolve(Constants.Fields.SRC_ADDR.getName()); Integer srcPortIn = (Integer) resolver.resolve(Constants.Fields.SRC_PORT.getName()); String dstAddrIn = (String) resolver.resolve(Constants.Fields.DST_ADDR.getName()); Integer dstPortIn = (Integer) resolver.resolve(Constants.Fields.DST_PORT.getName()); + String protocolIn = "" + resolver.resolve(Constants.Fields.PROTOCOL.getName()); + if(!doHeaderFiltering || testHeader(srcAddrIn, srcPortIn, dstAddrIn, dstPortIn, protocolIn)) { + //if we don't do header filtering *or* if we have tested the header and decided it's a match + if(packetFilter != null) { + //and we have a packet filter, then we need to filter the packet + byte[] data = (byte[])resolver.resolve(PcapHelper.PacketFields.PACKET_DATA.getName()); + try { + return ByteArrayMatchingUtil.INSTANCE.match(packetFilter, data); + } catch (ExecutionException e) { + throw new IllegalStateException("Unable to perform binary filter: " + packetFilter + " on " + DatatypeConverter.printHexBinary(data), e); + } + } + else if(!doHeaderFiltering){ + //otherwise we aren't doing packet filtering either, so we aren't doing any filtering, then we should + //pass the test + return true; + } + else { + //and if we *are* doing header filtering and not packet filtering, then we want to pass the test + return true; + } + } + else { + //in this case we're doing header filtering and we failed the header filter test. + return false; + } + } + private boolean testHeader( String srcAddrIn + , Integer srcPortIn + , String dstAddrIn + , Integer dstPortIn + , String protocolIn + ) { if (areMatch(protocol, protocolIn)) { if (matchesSourceAndDestination(srcAddrIn, srcPortIn, dstAddrIn, dstPortIn)) { return true; @@ -102,7 +157,7 @@ public class FixedPcapFilter implements PcapFilter { } private boolean areMatch(Integer filter, Integer input) { - return filter == null || areMatch(Integer.toUnsignedString(filter), Integer.toUnsignedString(input)); + return filter == null || areMatch(Integer.toUnsignedString(filter), input == null?null:Integer.toUnsignedString(input)); } private boolean areMatch(String filter, String input) { @@ -113,7 +168,7 @@ public class FixedPcapFilter implements PcapFilter { } } - protected EnumMap<Constants.Fields, Object> packetToFields(PacketInfo pi) { + protected Map<String, Object> packetToFields(PacketInfo pi) { return PcapHelper.packetToFields(pi); }
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/query/QueryPcapFilter.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/query/QueryPcapFilter.java b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/query/QueryPcapFilter.java index 28a8b93..d6fa203 100644 --- a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/query/QueryPcapFilter.java +++ b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/filter/query/QueryPcapFilter.java @@ -21,6 +21,7 @@ package org.apache.metron.pcap.filter.query; import org.apache.hadoop.conf.Configuration; import org.apache.metron.common.Constants; import org.apache.metron.common.dsl.Context; +import org.apache.metron.common.dsl.MapVariableResolver; import org.apache.metron.common.dsl.StellarFunctions; import org.apache.metron.common.stellar.StellarPredicateProcessor; import org.apache.metron.common.dsl.VariableResolver; @@ -69,12 +70,12 @@ public class QueryPcapFilter implements PcapFilter { @Override public boolean test(PacketInfo input) { - EnumMap<Constants.Fields, Object> fields = packetToFields(input); - VariableResolver resolver = new PcapFieldResolver(fields); + Map<String, Object> fields = packetToFields(input); + VariableResolver resolver = new MapVariableResolver(fields); return predicateProcessor.parse(queryString, resolver, StellarFunctions.FUNCTION_RESOLVER(), Context.EMPTY_CONTEXT()); } - protected EnumMap<Constants.Fields, Object> packetToFields(PacketInfo pi) { + protected Map<String, Object> packetToFields(PacketInfo pi) { return PcapHelper.packetToFields(pi); } } http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatcherFunction.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatcherFunction.java b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatcherFunction.java new file mode 100644 index 0000000..a4a74f3 --- /dev/null +++ b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatcherFunction.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.pcap.pattern; + +import org.apache.metron.common.dsl.Context; +import org.apache.metron.common.dsl.ParseException; +import org.apache.metron.common.dsl.Stellar; +import org.apache.metron.common.dsl.StellarFunction; + +import javax.xml.bind.DatatypeConverter; +import java.util.List; +import java.util.concurrent.ExecutionException; + +@Stellar(namespace="BYTEARRAY" + ,name="MATCHER" + ,description = "Determine if a regex defined sequence of bytes (or strings) exists in a byte array." + ,params = { + "binary_regex - Regex defining what to look for in the byte array. Note syntax guide for binary regex is at https://github.com/nishihatapalmer/byteseek/blob/master/sequencesyntax.md" + ,"data - The byte array to evaluate." + } + ,returns="result: Boolean indicating whether or not the byte array is a match." + ) +public class ByteArrayMatcherFunction implements StellarFunction { + @Override + public Object apply(List<Object> args, Context context) throws ParseException { + if(args.size() != 2) { + return new IllegalStateException("Expected 2 arguments: regex and data"); + } + String regex = (String)args.get(0); + byte[] data = (byte[])args.get(1); + try { + return ByteArrayMatchingUtil.INSTANCE.match(regex, data); + } + catch (ExecutionException e) { + throw new IllegalStateException("Unable to process " + regex + " against " + DatatypeConverter.printHexBinary(data)); + } + } + + @Override + public void initialize(Context context) { + + } + + @Override + public boolean isInitialized() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtil.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtil.java b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtil.java new file mode 100644 index 0000000..bd57b7c --- /dev/null +++ b/metron-platform/metron-pcap/src/main/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtil.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.pcap.pattern; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import net.byteseek.compiler.CompileException; +import net.byteseek.compiler.matcher.MatcherCompilerUtils; +import net.byteseek.compiler.matcher.SequenceMatcherCompiler; +import net.byteseek.matcher.sequence.SequenceMatcher; +import net.byteseek.searcher.Searcher; +import net.byteseek.searcher.sequence.horspool.BoyerMooreHorspoolSearcher; +import net.byteseek.searcher.sequence.horspool.HorspoolFinalFlagSearcher; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public enum ByteArrayMatchingUtil { + INSTANCE; + private LoadingCache<String, Searcher<SequenceMatcher>> sequenceMatchers = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build( + new CacheLoader<String, Searcher<SequenceMatcher>>() { + public Searcher<SequenceMatcher> load(String pattern) throws Exception { + return new HorspoolFinalFlagSearcher(compile(pattern)); + } + }); + private SequenceMatcherCompiler compiler = new SequenceMatcherCompiler(); + + private SequenceMatcher compile(String pattern) throws CompileException { + + return compiler.compile(pattern); + } + + public boolean match(String pattern, byte[] data) throws ExecutionException { + if(pattern == null) { + return false; + } + Searcher<SequenceMatcher> searcher = sequenceMatchers.get(pattern); + if(data == null) { + return false; + } + else { + return !searcher.searchForwards(data).isEmpty(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilterTest.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilterTest.java b/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilterTest.java index b75b9c8..af2afd3 100644 --- a/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilterTest.java +++ b/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/filter/fixed/FixedPcapFilterTest.java @@ -22,7 +22,7 @@ import org.apache.metron.common.Constants; import org.junit.Assert; import org.junit.Test; -import java.util.EnumMap; +import java.util.LinkedHashMap; import static org.hamcrest.CoreMatchers.equalTo; @@ -30,12 +30,12 @@ public class FixedPcapFilterTest { @Test public void string_representation_of_query_gets_formatted() throws Exception { - final EnumMap<Constants.Fields, String> fields = new EnumMap<Constants.Fields, String>(Constants.Fields.class) {{ - put(Constants.Fields.SRC_ADDR, "src_ip"); - put(Constants.Fields.SRC_PORT, "0"); - put(Constants.Fields.DST_ADDR, "dst_ip"); - put(Constants.Fields.DST_PORT, "1"); - put(Constants.Fields.INCLUDES_REVERSE_TRAFFIC, "false"); + final LinkedHashMap<String, String> fields = new LinkedHashMap<String, String>() {{ + put(Constants.Fields.SRC_ADDR.getName(), "src_ip"); + put(Constants.Fields.SRC_PORT.getName(), "0"); + put(Constants.Fields.DST_ADDR.getName(), "dst_ip"); + put(Constants.Fields.DST_PORT.getName(), "1"); + put(Constants.Fields.INCLUDES_REVERSE_TRAFFIC.getName(), "false"); }}; String actual = new FixedPcapFilter.Configurator().queryToString(fields); String expected = "src_ip_0_dst_ip_1_false"; @@ -45,7 +45,7 @@ public class FixedPcapFilterTest { @Test public void string_representation_of_empty_fields_empty() throws Exception { { - final EnumMap<Constants.Fields, String> fields = new EnumMap<Constants.Fields, String>(Constants.Fields.class); + final LinkedHashMap<String, String> fields = new LinkedHashMap<String, String>(); String actual = new FixedPcapFilter.Configurator().queryToString(fields); String expected = ""; Assert.assertThat("string representation did not match", actual, equalTo(expected)); @@ -56,9 +56,9 @@ public class FixedPcapFilterTest { Assert.assertThat("string representation did not match", actual, equalTo(expected)); } { - final EnumMap<Constants.Fields, String> fields = new EnumMap<Constants.Fields, String>(Constants.Fields.class) {{ - put(Constants.Fields.SRC_ADDR, ""); - put(Constants.Fields.SRC_PORT, ""); + final LinkedHashMap<String, String> fields = new LinkedHashMap<String, String>() {{ + put(Constants.Fields.SRC_ADDR.getName(), ""); + put(Constants.Fields.SRC_PORT.getName(), ""); }}; String actual = new FixedPcapFilter.Configurator().queryToString(fields); String expected = "_"; http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/bf2528fd/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtilTest.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtilTest.java b/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtilTest.java new file mode 100644 index 0000000..37b3f3f --- /dev/null +++ b/metron-platform/metron-pcap/src/test/java/org/apache/metron/pcap/pattern/ByteArrayMatchingUtilTest.java @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.pcap.pattern; + +import com.google.common.collect.ImmutableMap; +import org.apache.metron.common.utils.StellarProcessorUtils; +import org.apache.metron.pcap.pattern.ByteArrayMatchingUtil; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.xml.bind.DatatypeConverter; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; + +@RunWith(Parameterized.class) +public class ByteArrayMatchingUtilTest { + public static byte[] DEADBEEF = new byte[] {(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef}; + public static byte[] DEADBEEF_DONUTHOLE = new byte[] {(byte) 0xde, (byte) 0xad, (byte)0x00, (byte)0x00, (byte) 0xbe, (byte) 0xef}; + public static byte[] ALLFS = new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + public static byte[] REALPACKET = DatatypeConverter.parseHexBinary("d4c3b2a1020004000000000000000000ffff0000010000000dfbea560923090062000000620000000a002700000008002796a47a0800451000547cdf40004006b7e9c0a84279c0a842010016d9ef129035948b92f427801801f5061200000101080a0014f5d511bdf9915bf6b70140db6d4fb551229ef07d2f56abd814bc56420489ca38e7faf8cec3d4"); + + interface Evaluator { + boolean evaluate(String pattern, byte[] data); + } + + public enum EvaluationStrategy implements Evaluator{ + STELLAR_WITH_VARIABLES((pattern, data) -> { + Map<String, Object> args = new HashMap<>(); + args.put("pattern", pattern); + args.put("data", data); + return (boolean) StellarProcessorUtils.run("BYTEARRAY_MATCHER(pattern, data)" , args); + } + ), + STELLAR_WITH_PATTERN_STRING((pattern, data) -> { + Map<String, Object> args = new HashMap<>(); + args.put("data", data); + return (boolean) StellarProcessorUtils.run(String.format("BYTEARRAY_MATCHER('%s', data)", pattern) , args); + } + ) + , UTIL((pattern, data) -> { + try { + return ByteArrayMatchingUtil.INSTANCE.match(pattern, data); + } catch (ExecutionException e) { + throw new IllegalArgumentException(e); + } + }) + ; + Evaluator evaluator; + EvaluationStrategy(Evaluator evaluator) { + this.evaluator = evaluator; + } + @Override + public boolean evaluate(String pattern, byte[] data) { + return evaluator.evaluate(pattern, data); + } + } + private EvaluationStrategy strategy = null; + public ByteArrayMatchingUtilTest(EvaluationStrategy strategy) { + this.strategy = strategy; + } + + @Parameterized.Parameters + public static Collection<Object[]> strategies() { + List<Object[]> strategies = new ArrayList<>(); + for(EvaluationStrategy s : EvaluationStrategy.values()) { + strategies.add(new Object[] { s }); + } + return strategies; + } + + @Test + public void testStringMatch() throws ExecutionException { + Assert.assertTrue(strategy.evaluate("`metron`", "metron".getBytes())); + Assert.assertTrue(strategy.evaluate("`metron`", "metron example".getBytes())); + Assert.assertTrue(strategy.evaluate("`metron`", "edward metron example".getBytes())); + Assert.assertFalse(strategy.evaluate("`metron`", "apache".getBytes())); + } + + @Test + public void testBytesMatch() throws ExecutionException { + Assert.assertTrue(strategy.evaluate("2f56abd814bc56420489ca38e7faf8cec3d4", REALPACKET)); + Assert.assertTrue(strategy.evaluate("2f56..14bc56420489ca38e7faf8cec3d4", REALPACKET)); + Assert.assertTrue(strategy.evaluate("(2f56)(.){2}(14bc56420489ca38e7faf8cec3d4)", REALPACKET)); + Assert.assertFalse(strategy.evaluate("(3f56)(.){2}(14bc56420489ca38e7faf8cec3d4)", REALPACKET)); + Assert.assertFalse(strategy.evaluate("3f56abd814bc56420489ca38e7faf8cec3d4", REALPACKET)); + Assert.assertTrue(strategy.evaluate("deadbeef", join(DEADBEEF, "metron".getBytes()))); + Assert.assertTrue(strategy.evaluate("deadbeef", join(DEADBEEF, "metron".getBytes()))); + Assert.assertTrue(strategy.evaluate("deadbeef `metron`", join(DEADBEEF, "metron".getBytes()))); + Assert.assertTrue(strategy.evaluate("deadbeef `metron`", join(DEADBEEF, "metronjones".getBytes()))); + Assert.assertTrue(strategy.evaluate("deadbeef `metron`", join(DEADBEEF, "metronjones".getBytes(), DEADBEEF))); + Assert.assertTrue(strategy.evaluate("([ff]){4}", ALLFS)); + Assert.assertFalse(strategy.evaluate("([ff]){6}", ALLFS)); + Assert.assertTrue(strategy.evaluate("[^ff]", new byte[] { (byte)0x00 })); + Assert.assertTrue(strategy.evaluate("&01", new byte[] { (byte)0x07 })); + Assert.assertFalse(strategy.evaluate("&01", new byte[] { (byte)0x00 })); + Assert.assertTrue(strategy.evaluate("&01", new byte[] { (byte)0x00, (byte)0x01 })); + Assert.assertTrue(strategy.evaluate("(dead).{2}(beef)", DEADBEEF_DONUTHOLE)); + } + + public byte[] join(byte[]... array) { + byte[] ret; + int size = 0; + for(int i = 0;i < array.length;++i) { + size += array[i].length; + } + ret = new byte[size]; + int j = 0; + for(int i = 0;i < array.length;++i) { + for(int k = 0;k < array[i].length;++k,++j) { + ret[j] = array[i][k]; + } + } + return ret; + } +}
