Reorganize code by creating a package for code that reassembles traffic flows at...
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / detection / ClusterMatcher.java
index 26979092397fd53628f11b3462ffc8d09a59f75e..1ff2c0215b4eef77e7befe5d2afd0e60844bc6e8 100644 (file)
@@ -1,7 +1,7 @@
 package edu.uci.iotproject.detection;
 
-import edu.uci.iotproject.Conversation;
-import edu.uci.iotproject.TcpReassembler;
+import edu.uci.iotproject.trafficreassembly.layer3.Conversation;
+import edu.uci.iotproject.trafficreassembly.layer3.TcpReassembler;
 import edu.uci.iotproject.analysis.TcpConversationUtils;
 import edu.uci.iotproject.io.PcapHandleReader;
 import edu.uci.iotproject.util.PrintUtils;
@@ -83,31 +83,35 @@ public class ClusterMatcher implements PacketListener {
      *                          {@code cluster}.
      */
     public ClusterMatcher(List<List<PcapPacket>> 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
@@ -134,16 +138,6 @@ public class ClusterMatcher implements PacketListener {
                 // Skip empty conversations.
                 continue;
             }
-            // TODO: DEBUG!!!
-            /*List<PcapPacket> listPP = c.getPackets();
-            if(listPP.size() > 1000) {
-                for (PcapPacket pp : listPP) {
-                    if (pp.length() == 639) {
-                        boolean test = c.isTls();
-                        System.out.println("Sequence has 639! " + test);
-                    }
-                }
-            }*/
             for (List<PcapPacket> signatureSequence : mCluster) {
                 if (isTlsSequence(signatureSequence) != c.isTls()) {
                     // We consider it a mismatch if one is a TLS application data sequence and the other is not.
@@ -155,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<List<PcapPacket>> match;
-                boolean matchFound = false;
                 while ((match = findSubsequenceInSequence(signatureSequence, cPkts, mClusterMemberDirections, null)).
                         isPresent()) {
-                    matchFound = true;
                     List<PcapPacket> matchSeq = match.get();
                     // Notify observers about the match.
                     Arrays.stream(mObservers).forEach(o -> o.onMatch(ClusterMatcher.this, matchSeq));
@@ -181,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:
@@ -301,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<List<PcapPacket>> pruneCluster(List<List<PcapPacket>> cluster) {
+        List<List<PcapPacket>> prunedCluster = new ArrayList<>();
+        for (List<PcapPacket> originalClusterSeq : cluster) {
+            boolean alreadyPresent = false;
+            for (List<PcapPacket> prunedClusterSeq : prunedCluster) {
+                Optional<List<PcapPacket>> 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<PcapPacket>}, 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