1 package edu.uci.iotproject.util;
3 import edu.uci.iotproject.DnsMap;
4 import edu.uci.iotproject.analysis.PcapPacketPair;
5 import org.apache.commons.math3.stat.clustering.Cluster;
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.util.List;
12 import java.util.Optional;
14 import java.util.stream.Collectors;
16 import org.pcap4j.core.PcapPacket;
19 * Utility methods for generating (output) strings.
21 * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
22 * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
24 public class PrintUtils {
27 * This is the path for writing the list of list of packet pairs {@code List<List<PcapPacket>>} into a file.
28 * The packet pairs are the pairs in one cluster, so the list represents a cluster that has been derived through
29 * the DBSCAN algorithm.
31 * E.g., this file could contain a list like the following:
33 * [[1109, 613],[1111, 613],[1115, 613],...]
35 * This list has lists of PcapPacket pairs as its members. We do not maintain the pairs in the form of
36 * {@code Cluster<PcapPacketPair>} objects because there might be a situation where we could combine multiple
37 * PcapPacketPair objects into a longer signature, i.e., a string of PcapPacket objects and not just a pair.
39 private static final String SERIALIZABLE_FILE_PATH = "./signature.sig";
41 private PrintUtils() { /* private constructor to prevent instantiation */ }
44 * Write the list of list of packet pairs {@code List<List<PcapPacket>>} into a file.
46 * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
47 * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
48 * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
49 * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
50 * PcapPacket objects and not just a pair.
52 * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
53 * default file name {@code SERIALIZABLE_FILE_PATH}.
54 * @param clusterPackets The {@link Cluster} objects in the form of list of {@code PcapPacket} objects.
56 public static void serializeClustersIntoFile(String fileName, List<List<PcapPacket>> clusterPackets) {
58 fileName = SERIALIZABLE_FILE_PATH;
59 try (ObjectOutputStream oos =
60 new ObjectOutputStream(new FileOutputStream(fileName))) {
61 oos.writeObject(clusterPackets);
62 } catch (Exception ex) {
68 * Write the signature and cluster analysis {@code List<List<List<PcapPacket>>>} into a file.
70 * After the DBSCAN algorithm derives the clusters from pairs, we save the signature and cluster analysis
71 * in the form of list of packet pairs. We harvest the pairs and transform them back into a list of PcapPacket
73 * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
74 * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
75 * PcapPacket objects and not just a pair.
77 * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
78 * default file name {@code SERIALIZABLE_FILE_PATH}.
79 * @param signature The {@link Cluster} objects in the form of list of {@code PcapPacket} objects.
81 public static void serializeIntoFile(String fileName, List<List<List<PcapPacket>>> signature) {
83 fileName = SERIALIZABLE_FILE_PATH;
84 try (ObjectOutputStream oos =
85 new ObjectOutputStream(new FileOutputStream(fileName))) {
86 oos.writeObject(signature);
87 } catch (Exception ex) {
93 * Read the list of list of packet pairs {@code List<List<PcapPacket>>} from a file.
95 * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
96 * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
97 * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
98 * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
99 * PcapPacket objects and not just a pair.
101 * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
102 * default file name {@code SERIALIZABLE_FILE_PATH}.
103 * @return The list of list of {@link Cluster} objects ({@code List<List<PcapPacket>>}) that is read from file.
105 public static List<List<PcapPacket>> deserializeClustersFromFile(String fileName) {
106 if (fileName == null)
107 fileName = SERIALIZABLE_FILE_PATH;
108 List<List<PcapPacket>> ppListOfList = null;
109 try (ObjectInputStream ois =
110 new ObjectInputStream(new FileInputStream(fileName))) {
111 ppListOfList = (List<List<PcapPacket>>) ois.readObject();
112 } catch (Exception ex) {
113 ex.printStackTrace();
120 * Read the list of list of packet pairs {@code List<List<List<PcapPacket>>>} from a file.
122 * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
123 * default file name {@code SERIALIZABLE_FILE_PATH}.
124 * @return The list of list of list of {@link Cluster} objects ({@code List<List<List<PcapPacket>>>})
125 * that is read from file.
127 public static List<List<List<PcapPacket>>> deserializeFromFile(String fileName) {
128 if (fileName == null)
129 fileName = SERIALIZABLE_FILE_PATH;
130 List<List<List<PcapPacket>>> ppListOfListOfList = null;
131 try (ObjectInputStream ois =
132 new ObjectInputStream(new FileInputStream(fileName))) {
133 ppListOfListOfList = (List<List<List<PcapPacket>>>) ois.readObject();
134 } catch (Exception ex) {
135 ex.printStackTrace();
138 return ppListOfListOfList;
142 * Converts a {@code PcapPacketPair} into a CSV string containing the packet lengths of the two packets in the pair.
144 * For example, the resulting string will be "123, 456" if the first packet of the pair has a length of 123 and the
145 * second packet of the pair has a length of 456.
147 * <b>Note:</b> if the {@link PcapPacketPair} has no second element, 0 is printed as the length of the second packet
150 * @return a CSV string containing the packet lengths of the two packets of the given {@code PcapPacketPair}.
152 public static String toCsv(PcapPacketPair packetPair) {
153 return String.format("%d, %d", packetPair.getFirst().getOriginalLength(),
154 packetPair.getSecond().map(pp -> pp.getOriginalLength()).orElse(0));
158 * Converts a {@code PcapPacketPair} into a CSV string containing the packet lengths of the two packets in the pair
159 * followed by the source of each packet. The source will be a (set of) hostname(s) if the source IP can be resolved
160 * to a (set of) hostname(s) using the provided {@link DnsMap}.
162 * For example, the resulting string will be "123, 456, 192.168.1.42, domain.com" if the first packet of the pair
163 * has a length of 123, the second packet of the pair has a length of 456, the first packet of the pair the pair has
164 * a source IP of '192.168.1.42' that cannot be resolved to a hostname, and the second packet of the pair has an IP
165 * that resolves to 'domain.com'.
167 * <b>Note:</b> if the {@link PcapPacketPair} has no second element, 0 is printed as the length of the second packet
168 * in the pair, and null is printed for its source.
170 * @return a CSV string containing the packet lengths of the two packets of the given {@code PcapPacketPair} as well
171 * as their respective sources.
173 public static String toCsv(PcapPacketPair packetPair, DnsMap ipHostnameMappings) {
174 // First obtain source IPs
175 String firstSrc = PcapPacketUtils.getSourceIp(packetPair.getFirst());
176 // Note: use optional for second item in pair as there might not be one.
177 Optional<String> secondSrc = packetPair.getSecond().map(pkt -> PcapPacketUtils.getSourceIp(pkt));
179 // If possible, map source IPs to hostnames.
180 Set<String> firstHostnames = ipHostnameMappings.getHostnamesForIp(firstSrc);
181 Optional<Set<String>> secondHostnames = secondSrc.map(src -> ipHostnameMappings.getHostnamesForIp(src));
182 final String delimiter = " ";
183 if (firstHostnames != null) {
184 // If one IP maps to multiple hostnames, we concatenate the hostnames (separated by a delimiter).
185 firstSrc = firstHostnames.stream().collect(Collectors.joining(delimiter));
187 // If one IP maps to multiple hostnames, we concatenate the hostnames (separated by a delimiter).
188 Optional<String> hostnames = secondHostnames.map(hostnameSet -> hostnameSet.stream().collect(Collectors.joining(delimiter)));
189 // Fall back to IP if we couldn't second pair is present, but we couldn't map to (a) hostname(s).
190 secondSrc = hostnames.isPresent() ? hostnames : secondSrc;
192 // Check if the first source is C (client) or S (server).
193 String firstSrcCorS = packetPair.isFirstClient() ? "C" : "S";
194 String secondSrcCorS = packetPair.isSecondClient() ? "C" : "S";
196 return String.format("%d, %d, %s, %s, %s, %s", packetPair.getFirst().getOriginalLength(),
197 packetPair.getSecond().map(pp -> pp.getOriginalLength()).orElse(0),
199 secondSrc.orElse("null"),
205 * Generate a string that summarizes/describes {@code cluster}.
206 * @param cluster The {@link Cluster} to summarize/describe.
207 * @return A string that summarizes/describes {@code cluster}.
209 public static String toSummaryString(Cluster<PcapPacketPair> cluster) {
210 StringBuilder sb = new StringBuilder();
211 for (PcapPacketPair ppp : cluster.getPoints()) {
212 sb.append(toCsv(ppp, ppp.getDnsMap()) + System.lineSeparator());
214 return sb.toString();