Adding PacketLevelSignatureExtractor.
[pingpong.git] / Code / Projects / PacketLevelSignatureExtractor / src / main / java / edu / uci / iotproject / analysis / TriggerTrafficExtractor.java
1 package edu.uci.iotproject.analysis;
2
3 import edu.uci.iotproject.io.PcapHandleReader;
4 import org.pcap4j.core.*;
5
6 import java.time.Instant;
7 import java.util.Collections;
8 import java.util.List;
9 import java.util.concurrent.TimeoutException;
10
11 /**
12  * TODO add class documentation.
13  *
14  * @author Janus Varmarken
15  */
16 public class TriggerTrafficExtractor implements PcapPacketFilter {
17
18     private final String mPcapFilePath;
19     private final List<Instant> mTriggerTimes;
20     private final String mDeviceIp;
21
22     private int mTriggerIndex = 0;
23
24     /**
25      * The total number of packets marked for inclusion during one run of {@link #performExtraction(PacketListener...)}.
26      */
27     private long mIncludedPackets = 0;
28
29     public static final int INCLUSION_WINDOW_MILLIS = 15_000;
30
31     public TriggerTrafficExtractor(String pcapFilePath, List<Instant> triggerTimes, String deviceIp) throws PcapNativeException, NotOpenException {
32         mPcapFilePath = pcapFilePath;
33         // Ensure that trigger times are sorted in ascending as we rely on this fact in the logic that works out if a
34         // packet is related to a trigger.
35         Collections.sort(triggerTimes, (i1, i2) -> {
36             if (i1.isBefore(i2)) return -1;
37             else if (i2.isBefore(i1)) return 1;
38             else return 0;
39         });
40         mTriggerTimes = Collections.unmodifiableList(triggerTimes);
41         mDeviceIp = deviceIp;
42     }
43
44
45     public void performExtraction(PacketListener... extractedPacketsConsumers) throws PcapNativeException, NotOpenException, TimeoutException {
46         // Reset trigger index and packet counter in case client code chooses to rerun the extraction.
47         mTriggerIndex = 0;
48         mIncludedPackets = 0;
49         PcapHandle handle;
50         try {
51             handle = Pcaps.openOffline(mPcapFilePath, PcapHandle.TimestampPrecision.NANO);
52         } catch (PcapNativeException pne) {
53             handle = Pcaps.openOffline(mPcapFilePath);
54         }
55         // Use the native support for BPF to immediately filter irrelevant traffic.
56         handle.setFilter("ip host " + mDeviceIp, BpfProgram.BpfCompileMode.OPTIMIZE);
57         PcapHandleReader pcapReader = new PcapHandleReader(handle, this, extractedPacketsConsumers);
58         pcapReader.readFromHandle();
59
60     }
61
62     /**
63      * Return the number of extracted packets (i.e., packets selected for inclusion) as a result of the most recent call
64      * to {@link #performExtraction(PacketListener...)}.
65      *
66      * @return the number of extracted packets (i.e., packets selected for inclusion) as a result of the most recent
67      *         call to {@link #performExtraction(PacketListener...)}.
68      */
69     public long getPacketsIncludedCount() {
70         return mIncludedPackets;
71     }
72
73     @Override
74     public boolean shouldIncludePacket(PcapPacket packet) {
75         // New version. Simpler, but slower: the later a packet arrives, the more elements of mTriggerTimes will need to
76         // be traversed.
77         boolean include = mTriggerTimes.stream().anyMatch(
78                 trigger -> trigger.isBefore(packet.getTimestamp()) &&
79                         packet.getTimestamp().isBefore(trigger.plusMillis(INCLUSION_WINDOW_MILLIS))
80         );
81         if (include) {
82             mIncludedPackets++;
83         }
84         return include;
85
86         /*
87         // Old version. Faster, but more complex - is it correct?
88         if (mTriggerIndex >= mTriggerTimes.size()) {
89             // Don't include packet if we've exhausted the list of trigger times.
90             return false;
91         }
92
93         // TODO hmm, is this correct?
94         Instant trigger = mTriggerTimes.get(mTriggerIndex);
95         if (trigger.isBefore(packet.getTimestamp()) &&
96                 packet.getTimestamp().isBefore(trigger.plusMillis(INCLUSION_WINDOW_MILLIS))) {
97             // Packet lies within INCLUSION_WINDOW_MILLIS after currently considered trigger, include it.
98             return true;
99         } else {
100             if (!trigger.isBefore(packet.getTimestamp())) {
101                 // Packet is before currently considered trigger, so it shouldn't be included
102                 return false;
103             } else {
104                 // Packet is >= INCLUSION_WINDOW_MILLIS after currently considered trigger.
105                 // Proceed to next trigger to see if it lies in range of that.
106                 // Note that there's an assumption here that no two trigger intervals don't overlap!
107                 mTriggerIndex++;
108                 return shouldIncludePacket(packet);
109             }
110         }
111         */
112     }
113
114 }