From: Janus Varmarken Date: Thu, 8 Nov 2018 18:34:52 +0000 (-0800) Subject: prune clusters in order to allow detection of different pairs in same connection X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=b8807b11fbb3c210581bcf688670b2fa9a615361;p=pingpong.git prune clusters in order to allow detection of different pairs in same connection --- diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/detection/ClusterMatcher.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/detection/ClusterMatcher.java index 279ceea..25f3191 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/detection/ClusterMatcher.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/detection/ClusterMatcher.java @@ -83,31 +83,35 @@ public class ClusterMatcher implements PacketListener { * {@code cluster}. */ public ClusterMatcher(List> cluster, String routerWanIp, ClusterMatchObserver... detectionObservers) { - mCluster = Collections.unmodifiableList(Objects.requireNonNull(cluster, "cluster cannot be null")); - mObservers = Objects.requireNonNull(detectionObservers, "detectionObservers cannot be null"); - if (mCluster.isEmpty() || mCluster.stream().anyMatch(inner -> inner.isEmpty())) { + // ===================== PRECONDITION SECTION ===================== + cluster = Objects.requireNonNull(cluster, "cluster cannot be null"); + if (cluster.isEmpty() || cluster.stream().anyMatch(inner -> inner.isEmpty())) { throw new IllegalArgumentException("cluster is empty (or contains an empty inner List)"); } + mObservers = Objects.requireNonNull(detectionObservers, "detectionObservers cannot be null"); if (mObservers.length == 0) { throw new IllegalArgumentException("no detectionObservers provided"); } - mRouterWanIp = routerWanIp; // Build the cluster members' direction sequence. // Note: assumes that the provided cluster was captured within the local network (routerWanIp is set to null). - mClusterMemberDirections = getPacketDirections(mCluster.get(0), null); + mClusterMemberDirections = getPacketDirections(cluster.get(0), null); /* * Enforce restriction on cluster members: all representatives must exhibit the same direction pattern and * contain the same number of packets. Note that this is a somewhat heavy operation, so it may be disabled later * on in favor of performance. However, it is only run once (at instantiation), so the overhead may be warranted * in order to ensure correctness, especially during the development/debugging phase. */ - if (mCluster.stream(). + if (cluster.stream(). anyMatch(inner -> !Arrays.equals(mClusterMemberDirections, getPacketDirections(inner, null)))) { throw new IllegalArgumentException( "cluster members must contain the same number of packets and exhibit the same packet direction " + "pattern" ); } + // ================================================================ + // Prune the provided cluster. + mCluster = pruneCluster(cluster); + mRouterWanIp = routerWanIp; } @Override @@ -145,21 +149,13 @@ public class ClusterMatcher implements PacketListener { * Note: we embed the attempt to detect the signature sequence in a loop in order to capture those cases * where the same signature sequence appears multiple times in one Conversation. * - * Note: as the cluster can be made up of identical sequences, we must keep track of whether we detected - * a match and, if so, break the inner for-each loop in order to prevent raising an alarm for each - * cluster-member (prevent duplicate detections of the same event). However, a negative side-effect of - * this is that, in doing so, we will also skip searching for subsequent different cluster members in - * the current conversation if the current cluster member is a match. - * * Note: since we expect all sequences that together make up the signature to exhibit the same direction * pattern, we can simply pass the precomputed direction array for the signature sequence so that it * won't have to be recomputed internally in each call to findSubsequenceInSequence(). */ Optional> match; - boolean matchFound = false; while ((match = findSubsequenceInSequence(signatureSequence, cPkts, mClusterMemberDirections, null)). isPresent()) { - matchFound = true; List matchSeq = match.get(); // Notify observers about the match. Arrays.stream(mObservers).forEach(o -> o.onMatch(ClusterMatcher.this, matchSeq)); @@ -171,10 +167,6 @@ public class ClusterMatcher implements PacketListener { // We restart the search for the signature sequence immediately after that index, so truncate cPkts. cPkts = cPkts.stream().skip(matchSeqEndIdx + 1).collect(Collectors.toList()); } - if (matchFound) { - // Break inner for-each loop in order to avoid duplicate detection of same event (see comment above) - break; - } } /* * TODO: @@ -291,6 +283,35 @@ public class ClusterMatcher implements PacketListener { return Optional.empty(); } + /** + * Given a cluster, produces a pruned version of that cluster. In the pruned version, there are no duplicate cluster + * members. Two cluster members are considered identical if their packets lengths and packet directions are + * identical. The resulting pruned cluster is unmodifiable (this applies to both the outermost list as well as the + * nested lists) in order to preserve its integrity when exposed to external code (e.g., through + * {@link #getCluster()}). + * + * @param cluster A cluster to prune. + * @return The resulting pruned cluster. + */ + private final List> pruneCluster(List> cluster) { + List> prunedCluster = new ArrayList<>(); + for (List originalClusterSeq : cluster) { + boolean alreadyPresent = false; + for (List prunedClusterSeq : prunedCluster) { + Optional> duplicate = findSubsequenceInSequence(originalClusterSeq, prunedClusterSeq, + mClusterMemberDirections, mClusterMemberDirections); + if (duplicate.isPresent()) { + alreadyPresent = true; + break; + } + } + if (!alreadyPresent) { + prunedCluster.add(Collections.unmodifiableList(originalClusterSeq)); + } + } + return Collections.unmodifiableList(prunedCluster); + } + /** * Given a {@code List}, generate a {@code Conversation.Direction[]} such that each entry in the * resulting {@code Conversation.Direction[]} specifies the direction of the {@link PcapPacket} at the corresponding