Modifying the value DELETED_SEQUENCES_OFF in the script from 0 to 1 as per bug/error...
[pingpong.git] / Code / Projects / PacketLevelSignatureExtractor / src / main / java / edu / uci / iotproject / SignatureGenerator.java
1 package edu.uci.iotproject;
2
3 import static edu.uci.iotproject.analysis.UserAction.Type;
4
5 import edu.uci.iotproject.analysis.*;
6 import edu.uci.iotproject.io.PrintWriterUtils;
7 import edu.uci.iotproject.io.TriggerTimesFileReader;
8 import edu.uci.iotproject.trafficreassembly.layer3.Conversation;
9 import edu.uci.iotproject.trafficreassembly.layer3.TcpReassembler;
10 import edu.uci.iotproject.util.PcapPacketUtils;
11 import edu.uci.iotproject.util.PrintUtils;
12 import org.apache.commons.math3.stat.clustering.Cluster;
13 import org.apache.commons.math3.stat.clustering.DBSCANClusterer;
14 import org.pcap4j.core.*;
15 import org.pcap4j.packet.namednumber.DataLinkType;
16
17 import java.io.*;
18 import java.net.UnknownHostException;
19 import java.time.Duration;
20 import java.time.Instant;
21 import java.util.*;
22 import java.util.concurrent.TimeoutException;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 /**
27  * This is a system that reads PCAP files to compare
28  * patterns of DNS hostnames, packet sequences, and packet
29  * lengths with training data to determine certain events
30  * or actions for smart home devices.
31  *
32  * Notes:
33  * This first version of software is created based on binary events (i.e., ON/OFF).
34  * We first tested it with the TP-Link plug. The events ON and OFF were generated alternately for 100 times using
35  * the automation scripts (see the automation folder). Thus, this program tries to extract signatures by
36  * assuming that:
37  *
38  * (1) there are 2 types of events (binary), and
39  * (2) the two types are triggered alternately.
40  *
41  * Hence, for non-binary events, we still can trigger the same event for 100 times and PingPong still groups them
42  * in 2 groups of 50 occurrences each. Please keep in mind that the 2 groups are simply named "ON" and "OFF"
43  * in this program although we tested PingPong against other types of events. For non-binary events (e.g., Intensity)
44  * we still trigger the phone app alternately between two values---to have a comprehensive understanding, we alternately
45  * triggered the intensity values of 1% and 100%. We suspect that the variation in lengths are due to the different
46  * lengths of the string "1%" to "100%".
47  *
48  * @author Janus Varmarken
49  * @author Rahmadi Trimananda (rtrimana@uci.edu)
50  * @version 1.0
51  */
52 public class SignatureGenerator {
53
54     /**
55      * If set to {@code true}, output written to the results file is also dumped to standard out.
56      */
57     private static boolean DUPLICATE_OUTPUT_TO_STD_OUT = true;
58     /**
59      * File name for logging.
60      */
61     private static String LOG_EXTENSION = "_signature-generation.log";
62     /**
63      * Directory for logging.
64      */
65     private static String LOG_DIRECTORY = "./";
66     /**
67      * Multiplier for cluster bounds.
68      */
69     private static float CLUSTER_BOUNDS_MULTIPLIER = 0.1f;
70
71     public static void main(String[] args) throws PcapNativeException, NotOpenException, EOFException,
72             TimeoutException, UnknownHostException, IOException {
73         // -------------------------------------------------------------------------------------------------------------
74         // ------------ # Code for extracting traffic generated by a device within x seconds of a trigger # ------------
75         if (args.length < 11) {
76             String errMsg = String.format("SPECTO version 1.0\n" +
77                             "Copyright (C) 2018-2019 Janus Varmarken and Rahmadi Trimananda.\n" +
78                             "University of California, Irvine.\n" +
79                             "All rights reserved.\n\n" +
80                             "Usage: %s inputPcapFile outputPcapFile triggerTimesFile deviceIp" +
81                             " onSignatureFile offSignatureFile onClusterAnalysisFile offClusterAnalysisFile epsilon" +
82                             " deletedSequencesOn deletedSequencesOff" +
83                             "\n  inputPcapFile: the target of the detection" +
84                             "\n  outputPcapFile: the processed PCAP file through 15-second window filtering" +
85                             "\n  triggerTimesFile: the trigger timestamps" +
86                             "\n  deviceIp: the IP address of the device we want to generate a signature for" +
87                             "\n  onSignatureFile: name of the ON signature file" +
88                             "\n  offSignatureFile: name of the OFF signature file" +
89                             "\n  onClusterAnalysisFile: name of the ON signature cluster analysis file" +
90                             "\n  offClusterAnalysisFile: name of the OFF signature cluster analysis file" +
91                             "\n  epsilon: epsilon value of the DBSCAN algorithm" +
92                             "\n  deletedSequencesOn: sequences to be deleted from the final ON signature" +
93                             " (please separate with commas, e.g., 0,1,2, or put '-1' if not needed)" +
94                             "\n  deletedSequencesOff: sequences to be deleted from the final OFF signature" +
95                             " (please separate with commas, e.g., 0,1,2, or put '-1' if not needed)",
96                     SignatureGenerator.class.getSimpleName());
97             System.out.println(errMsg);
98             return;
99         }
100         boolean verbose = true;
101         final String inputPcapFile = args[0];
102         final String outputPcapFile = args[1];
103         final String triggerTimesFile = args[2];
104         final String deviceIp = args[3];
105         final String onSignatureFile = args[4];
106         final String offSignatureFile = args[5];
107         final String onClusterAnalysisFile = args[6];
108         final String offClusterAnalysisFile = args[7];
109         final double eps = Double.parseDouble(args[8]);
110         final String deletedSequencesOn = args[9];
111         final String deletedSequencesOff = args[10];
112         final String logFile = inputPcapFile + LOG_EXTENSION;
113
114         // Prepare file outputter.
115         File outputFile = new File(logFile);
116         outputFile.getParentFile().mkdirs();
117         final PrintWriter resultsWriter = new PrintWriter(new FileWriter(outputFile));
118
119         // =========================================== TRAFFIC FILTERING ============================================
120
121         TriggerTimesFileReader ttfr = new TriggerTimesFileReader();
122         List<Instant> triggerTimes = ttfr.readTriggerTimes(triggerTimesFile, false);
123         // Tag each trigger with "ON" or "OFF", assuming that the first trigger is an "ON" and that they alternate.
124         List<UserAction> userActions = new ArrayList<>();
125         for (int i = 0; i < triggerTimes.size(); i++) {
126             userActions.add(new UserAction(i % 2 == 0 ? Type.TOGGLE_ON : Type.TOGGLE_OFF, triggerTimes.get(i)));
127         }
128         TriggerTrafficExtractor tte = new TriggerTrafficExtractor(inputPcapFile, triggerTimes, deviceIp);
129         final PcapDumper outputter = Pcaps.openDead(DataLinkType.EN10MB, 65536).dumpOpen(outputPcapFile);
130         DnsMap dnsMap = new DnsMap();
131         TcpReassembler tcpReassembler = new TcpReassembler();
132         TrafficLabeler trafficLabeler = new TrafficLabeler(userActions);
133         tte.performExtraction(pkt -> {
134             try {
135                 outputter.dump(pkt);
136             } catch (NotOpenException e) {
137                 e.printStackTrace();
138             }
139         }, dnsMap, tcpReassembler, trafficLabeler);
140         outputter.flush();
141         outputter.close();
142
143         if (tte.getPacketsIncludedCount() != trafficLabeler.getTotalPacketCount()) {
144             // Sanity/debug check
145             throw new AssertionError(String.format("mismatch between packet count in %s and %s",
146                     TriggerTrafficExtractor.class.getSimpleName(), TrafficLabeler.class.getSimpleName()));
147         }
148
149         // Extract all conversations present in the filtered trace.
150         List<Conversation> allConversations = tcpReassembler.getTcpConversations();
151         // Group conversations by hostname.
152         Map<String, List<Conversation>> convsByHostname =
153                 TcpConversationUtils.groupConversationsByHostname(allConversations, dnsMap);
154         PrintWriterUtils.println("Grouped conversations by hostname.", resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
155         // For each hostname, count the frequencies of packet lengths exchanged with that hostname.
156         final Map<String, Map<Integer, Integer>> pktLenFreqsByHostname = new HashMap<>();
157         convsByHostname.forEach((host, convs) -> pktLenFreqsByHostname.put(host,
158                 TcpConversationUtils.countPacketLengthFrequencies(convs)));
159         PrintWriterUtils.println("Counted frequencies of packet lengths exchanged with each hostname.",
160                 resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
161         // For each hostname, count the frequencies of packet sequences (i.e., count how many
162         // conversations exchange a sequence of packets of some specific lengths).
163         final Map<String, Map<String, Integer>> pktSeqFreqsByHostname = new HashMap<>();
164         convsByHostname.forEach((host, convs) -> pktSeqFreqsByHostname.put(host,
165                 TcpConversationUtils.countPacketSequenceFrequencies(convs)));
166         PrintWriterUtils.println("Counted frequencies of packet sequences exchanged with each hostname.",
167                 resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
168         // For each hostname, count frequencies of packet pairs exchanged
169         // with that hostname across all conversations
170         final Map<String, Map<String, Integer>> pktPairFreqsByHostname =
171                 TcpConversationUtils.countPacketPairFrequenciesByHostname(allConversations, dnsMap);
172         PrintWriterUtils.println("Counted frequencies of packet pairs per hostname.",
173                 resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
174         // For each user action, reassemble the set of TCP connections occurring shortly after
175         final Map<UserAction, List<Conversation>> userActionToConversations =
176                 trafficLabeler.getLabeledReassembledTcpTraffic();
177         final Map<UserAction, Map<String, List<Conversation>>> userActionsToConvsByHostname =
178                 trafficLabeler.getLabeledReassembledTcpTraffic(dnsMap);
179         PrintWriterUtils.println("Reassembled TCP conversations occurring shortly after each user event.",
180                 resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
181
182         /*
183          * NOTE: no need to generate these more complex on/off maps that also contain mappings from hostname and
184          * sequence identifiers as we do not care about hostnames and sequences during clustering.
185          * We can simply use the UserAction->List<Conversation> map to generate ON/OFF groupings of conversations.
186          */
187         // Contains all ON events: hostname -> sequence identifier -> list of conversations with that sequence
188         Map<String, Map<String, List<Conversation>>> ons = new HashMap<>();
189         // Contains all OFF events: hostname -> sequence identifier -> list of conversations with that sequence
190         Map<String, Map<String, List<Conversation>>> offs = new HashMap<>();
191         userActionsToConvsByHostname.forEach((ua, hostnameToConvs) -> {
192             Map<String, Map<String, List<Conversation>>> outer = ua.getType() == Type.TOGGLE_ON ? ons : offs;
193             hostnameToConvs.forEach((host, convs) -> {
194                 Map<String, List<Conversation>> seqsToConvs = TcpConversationUtils.
195                         groupConversationsByPacketSequence(convs, verbose);
196                 outer.merge(host, seqsToConvs, (oldMap, newMap) -> {
197                     newMap.forEach((sequence, cs) -> oldMap.merge(sequence, cs, (list1, list2) -> {
198                         list1.addAll(list2);
199                         return list1;
200                     }));
201                     return oldMap;
202                 });
203             });
204         });
205
206         // ============================================== PAIR CLUSTERING ============================================
207         // TODO: No need to use the more convoluted on/off maps; Can simply use the UserAction->List<Conversation> map
208         // TODO: when don't care about hostnames and sequences (see comment earlier).
209         // ===========================================================================================================
210         List<Conversation> onConversations = userActionToConversations.entrySet().stream().
211                 filter(e -> e.getKey().getType() == Type.TOGGLE_ON). // drop all OFF events from stream
212                 map(e -> e.getValue()). // no longer interested in the UserActions
213                 flatMap(List::stream). // flatten List<List<T>> to a List<T>
214                 collect(Collectors.toList());
215         List<Conversation> offConversations = userActionToConversations.entrySet().stream().
216                 filter(e -> e.getKey().getType() == Type.TOGGLE_OFF).
217                 map(e -> e.getValue()).
218                 flatMap(List::stream).
219                 collect(Collectors.toList());
220         //Collections.sort(onConversations, (c1, c2) -> c1.getPackets().)
221
222         List<PcapPacketPair> onPairs = onConversations.stream().
223                 map(c -> c.isTls() ? TcpConversationUtils.extractTlsAppDataPacketPairs(c) :
224                         TcpConversationUtils.extractPacketPairs(c)).
225                 flatMap(List::stream). // flatten List<List<>> to List<>
226                 collect(Collectors.toList());
227         List<PcapPacketPair> offPairs = offConversations.stream().
228                 map(c -> c.isTls() ? TcpConversationUtils.extractTlsAppDataPacketPairs(c) :
229                         TcpConversationUtils.extractPacketPairs(c)).
230                 flatMap(List::stream). // flatten List<List<>> to List<>
231                 collect(Collectors.toList());
232         // Note: need to update the DnsMap of all PcapPacketPairs if we want to use the IP/hostname-sensitive distance.
233         Stream.concat(Stream.of(onPairs), Stream.of(offPairs)).flatMap(List::stream).forEach(p -> p.setDnsMap(dnsMap));
234         // Perform clustering on conversation logged as part of all ON events.
235         // Calculate number of events per type (only ON/only OFF), which means half of the number of all timestamps.
236         int numberOfEventsPerType = triggerTimes.size() / 2;
237         int lowerBound = numberOfEventsPerType - (int)(numberOfEventsPerType * CLUSTER_BOUNDS_MULTIPLIER);
238         int upperBound = numberOfEventsPerType + (int)(numberOfEventsPerType * CLUSTER_BOUNDS_MULTIPLIER);
239         int minPts = lowerBound;
240         DBSCANClusterer<PcapPacketPair> onClusterer = new DBSCANClusterer<>(eps, minPts);
241         List<Cluster<PcapPacketPair>> onClusters = onClusterer.cluster(onPairs);
242         // Perform clustering on conversation logged as part of all OFF events.
243         DBSCANClusterer<PcapPacketPair> offClusterer = new DBSCANClusterer<>(eps, minPts);
244         List<Cluster<PcapPacketPair>> offClusters = offClusterer.cluster(offPairs);
245         // Sort the conversations as reference
246         List<Conversation> sortedAllConversation = TcpConversationUtils.sortConversationList(allConversations);
247         // Output clusters
248         PrintWriterUtils.println("========================================", resultsWriter,
249                 DUPLICATE_OUTPUT_TO_STD_OUT);
250         PrintWriterUtils.println("       Clustering results for ON        ", resultsWriter,
251                 DUPLICATE_OUTPUT_TO_STD_OUT);
252         PrintWriterUtils.println("       Number of clusters: " + onClusters.size(), resultsWriter,
253                 DUPLICATE_OUTPUT_TO_STD_OUT);
254         int count = 0;
255         List<List<List<PcapPacket>>> ppListOfListReadOn = new ArrayList<>();
256         List<List<List<PcapPacket>>> ppListOfListListOn = new ArrayList<>();
257         List<List<List<PcapPacket>>> corePointRangeSignatureOn = new ArrayList<>();
258         for (Cluster<PcapPacketPair> c : onClusters) {
259             PrintWriterUtils.println(String.format("<<< Cluster #%02d (%03d points) >>>", ++count, c.getPoints().size()),
260                     resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
261             PrintWriterUtils.println(PrintUtils.toSummaryString(c), resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
262             if(c.getPoints().size() > lowerBound && c.getPoints().size() < upperBound) {
263                 // Print to file
264                 List<List<PcapPacket>> ppListOfList = PcapPacketUtils.clusterToListOfPcapPackets(c);
265                 // Check for overlaps and decide whether to do range-based or conservative checking
266                 corePointRangeSignatureOn.add(PcapPacketUtils.extractRangeCorePoints(ppListOfList, eps, minPts));
267                 ppListOfListListOn.add(ppListOfList);
268             }
269         }
270         PrintWriterUtils.println("========================================", resultsWriter,
271                 DUPLICATE_OUTPUT_TO_STD_OUT);
272         PrintWriterUtils.println("       Clustering results for OFF        ", resultsWriter,
273                 DUPLICATE_OUTPUT_TO_STD_OUT);
274         PrintWriterUtils.println("       Number of clusters: " + offClusters.size(), resultsWriter,
275                 DUPLICATE_OUTPUT_TO_STD_OUT);
276         count = 0;
277         List<List<List<PcapPacket>>> ppListOfListReadOff = new ArrayList<>();
278         List<List<List<PcapPacket>>> ppListOfListListOff = new ArrayList<>();
279         List<List<List<PcapPacket>>> corePointRangeSignatureOff = new ArrayList<>();
280         for (Cluster<PcapPacketPair> c : offClusters) {
281             PrintWriterUtils.println(String.format("<<< Cluster #%03d (%06d points) >>>", ++count, c.getPoints().size()),
282                     resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
283             PrintWriterUtils.println(PrintUtils.toSummaryString(c), resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
284             if(c.getPoints().size() > lowerBound && c.getPoints().size() < upperBound) {
285                 // Print to file
286                 List<List<PcapPacket>> ppListOfList = PcapPacketUtils.clusterToListOfPcapPackets(c);
287                 // Check for overlaps and decide whether to do range-based or conservative checking
288                 corePointRangeSignatureOff.add(PcapPacketUtils.extractRangeCorePoints(ppListOfList, eps, minPts));
289                 ppListOfListListOff.add(ppListOfList);
290             }
291         }
292
293         // =========================================== SIGNATURE CREATION ===========================================
294         // Concatenate
295         ppListOfListListOn = PcapPacketUtils.concatSequences(ppListOfListListOn, sortedAllConversation);
296         // Remove sequences in the list that have overlap
297         StringTokenizer stringTokenizerOn = new StringTokenizer(deletedSequencesOn, ",");
298         while(stringTokenizerOn.hasMoreTokens()) {
299             int sequenceToDelete = Integer.parseInt(stringTokenizerOn.nextToken());
300             if (sequenceToDelete == -1) { // '-1' means there is no removal
301                 break;
302             }
303             PcapPacketUtils.removeSequenceFromSignature(ppListOfListListOn, sequenceToDelete);
304         }
305         PrintWriterUtils.println("ON Sequences: ", resultsWriter,
306                 DUPLICATE_OUTPUT_TO_STD_OUT);
307         for(List<List<PcapPacket>> listOfList : ppListOfListListOn) {
308             PrintWriterUtils.println(listOfList.get(0).get(0).length() + "...", resultsWriter,
309                     DUPLICATE_OUTPUT_TO_STD_OUT);
310         }
311         ppListOfListListOn = PcapPacketUtils.sortSequences(ppListOfListListOn);
312         PrintWriterUtils.println("Concatenated and sorted ON signature sequences...", resultsWriter,
313                 DUPLICATE_OUTPUT_TO_STD_OUT);
314
315         // Concatenate
316         ppListOfListListOff = PcapPacketUtils.concatSequences(ppListOfListListOff, sortedAllConversation);
317         // Remove sequences in the list that have overlap
318         StringTokenizer stringTokenizerOff = new StringTokenizer(deletedSequencesOff, ",");
319         while(stringTokenizerOff.hasMoreTokens()) {
320             int sequenceToDelete = Integer.parseInt(stringTokenizerOff.nextToken());
321             if (sequenceToDelete == -1) { // '-1' means there is no removal
322                 break;
323             }
324             PcapPacketUtils.removeSequenceFromSignature(ppListOfListListOff, sequenceToDelete);
325         }
326         PrintWriterUtils.println("OFF Sequences: ", resultsWriter,
327                 DUPLICATE_OUTPUT_TO_STD_OUT);
328         for(List<List<PcapPacket>> listOfList : ppListOfListListOff) {
329             PrintWriterUtils.println(listOfList.get(0).get(0).length() + "...", resultsWriter,
330                     DUPLICATE_OUTPUT_TO_STD_OUT);
331         }
332         ppListOfListListOff = PcapPacketUtils.sortSequences(ppListOfListListOff);
333         PrintWriterUtils.println("Concatenated and sorted OFF signature sequences...", resultsWriter,
334                 DUPLICATE_OUTPUT_TO_STD_OUT);
335
336         // Write the signatures into the screen
337         PrintWriterUtils.println("========================================", resultsWriter,
338                 DUPLICATE_OUTPUT_TO_STD_OUT);
339         PrintWriterUtils.println("              ON Signature              ", resultsWriter,
340                 DUPLICATE_OUTPUT_TO_STD_OUT);
341         PrintWriterUtils.println("========================================", resultsWriter,
342                 DUPLICATE_OUTPUT_TO_STD_OUT);
343         PcapPacketUtils.printSignatures(ppListOfListListOn, resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
344         PrintWriterUtils.println("========================================", resultsWriter,
345                 DUPLICATE_OUTPUT_TO_STD_OUT);
346         PrintWriterUtils.println("              OFF Signature             ", resultsWriter,
347                 DUPLICATE_OUTPUT_TO_STD_OUT);
348         PrintWriterUtils.println("========================================", resultsWriter,
349                 DUPLICATE_OUTPUT_TO_STD_OUT);
350         PcapPacketUtils.printSignatures(ppListOfListListOff, resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
351         // Clean signatures from null elements
352         PcapPacketUtils.cleanSignature(ppListOfListListOn);
353         PcapPacketUtils.cleanSignature(ppListOfListListOff);
354         // Printing signatures into files
355         PrintUtils.serializeIntoFile(onSignatureFile, ppListOfListListOn);
356         PrintUtils.serializeIntoFile(offSignatureFile, ppListOfListListOff);
357         // Printing cluster analyses into files
358         PrintUtils.serializeIntoFile(onClusterAnalysisFile, corePointRangeSignatureOn);
359         PrintUtils.serializeIntoFile(offClusterAnalysisFile, corePointRangeSignatureOff);
360
361         // =========================================== SIGNATURE DURATIONS =============================================
362         List<Instant> firstSignatureTimestamps = new ArrayList<>();
363         List<Instant> lastSignatureTimestamps = new ArrayList<>();
364         if (!ppListOfListListOn.isEmpty()) {
365             List<List<PcapPacket>> firstListOnSign = ppListOfListListOn.get(0);
366             List<List<PcapPacket>> lastListOnSign = ppListOfListListOn.get(ppListOfListListOn.size() - 1);
367             // Load ON signature first and last packet's timestamps
368             for (List<PcapPacket> list : firstListOnSign) {
369                 // Get timestamp Instant from the last packet
370                 firstSignatureTimestamps.add(list.get(0).getTimestamp());
371             }
372             for (List<PcapPacket> list : lastListOnSign) {
373                 // Get timestamp Instant from the last packet
374                 int lastPacketIndex = list.size() - 1;
375                 lastSignatureTimestamps.add(list.get(lastPacketIndex).getTimestamp());
376             }
377         }
378
379         if (!ppListOfListListOff.isEmpty()) {
380             List<List<PcapPacket>> firstListOffSign = ppListOfListListOff.get(0);
381             List<List<PcapPacket>> lastListOffSign = ppListOfListListOff.get(ppListOfListListOff.size() - 1);
382             // Load OFF signature first and last packet's timestamps
383             for (List<PcapPacket> list : firstListOffSign) {
384                 // Get timestamp Instant from the last packet
385                 firstSignatureTimestamps.add(list.get(0).getTimestamp());
386             }
387             for (List<PcapPacket> list : lastListOffSign) {
388                 // Get timestamp Instant from the last packet
389                 int lastPacketIndex = list.size() - 1;
390                 lastSignatureTimestamps.add(list.get(lastPacketIndex).getTimestamp());
391             }
392         }
393         // Sort the timestamps
394         firstSignatureTimestamps.sort(Comparator.comparing(Instant::toEpochMilli));
395         lastSignatureTimestamps.sort(Comparator.comparing(Instant::toEpochMilli));
396
397         Iterator<Instant> iterFirst = firstSignatureTimestamps.iterator();
398         Iterator<Instant> iterLast = lastSignatureTimestamps.iterator();
399         long duration;
400         long maxDuration = Long.MIN_VALUE;
401         PrintWriterUtils.println("========================================", resultsWriter,
402                 DUPLICATE_OUTPUT_TO_STD_OUT);
403         PrintWriterUtils.println("           Signature Durations          ", resultsWriter,
404                 DUPLICATE_OUTPUT_TO_STD_OUT);
405         PrintWriterUtils.println("========================================", resultsWriter,
406                 DUPLICATE_OUTPUT_TO_STD_OUT);
407         while (iterFirst.hasNext() && iterLast.hasNext()) {
408             Instant firstInst = iterFirst.next();
409             Instant lastInst = iterLast.next();
410             Duration dur = Duration.between(firstInst, lastInst);
411             duration = dur.toMillis();
412             // Check duration --- should be below 15 seconds
413             if (duration > TriggerTrafficExtractor.INCLUSION_WINDOW_MILLIS) {
414                 while (duration > TriggerTrafficExtractor.INCLUSION_WINDOW_MILLIS && iterFirst.hasNext()) {
415                     // that means we have to move to the next trigger
416                     firstInst = iterFirst.next();
417                     dur = Duration.between(firstInst, lastInst);
418                     duration = dur.toMillis();
419                 }
420             } else { // Below 0/Negative --- that means we have to move to the next signature
421                 while (duration < 0 && iterLast.hasNext()) {
422                     // that means we have to move to the next trigger
423                     lastInst = iterLast.next();
424                     dur = Duration.between(firstInst, lastInst);
425                     duration = dur.toMillis();
426                 }
427             }
428             PrintWriterUtils.println(duration, resultsWriter, DUPLICATE_OUTPUT_TO_STD_OUT);
429             // Update duration if this bigger than the max value and still less than the window inclusion time
430             maxDuration = maxDuration < duration && duration <= TriggerTrafficExtractor.INCLUSION_WINDOW_MILLIS ?
431                     duration : maxDuration;
432         }
433         // Just assign the value 0 if there is no signature
434         if (maxDuration == Long.MIN_VALUE) {
435             maxDuration = 0;
436         }
437         PrintWriterUtils.println("========================================", resultsWriter,
438                 DUPLICATE_OUTPUT_TO_STD_OUT);
439         PrintWriterUtils.println("Max signature duration: " + maxDuration, resultsWriter,
440                 DUPLICATE_OUTPUT_TO_STD_OUT);
441         PrintWriterUtils.println("========================================", resultsWriter,
442                 DUPLICATE_OUTPUT_TO_STD_OUT);
443         resultsWriter.flush();
444         resultsWriter.close();
445         // ==========================================================================================================
446     }
447 }