From: rtrimana Date: Tue, 31 Jul 2018 23:40:51 +0000 (-0700) Subject: Merge branch 'master' of https://github.uci.edu/rtrimana/smart_home_traffic X-Git-Url: http://plrg.eecs.uci.edu/git/?p=pingpong.git;a=commitdiff_plain;h=3730251c1696f843899313f3f7cd56fc31d0f828;hp=af8c8476359986b7d5851777805aeaf5073605c9 Merge branch 'master' of https://github.uci.edu/rtrimana/smart_home_traffic --- diff --git a/Code/Projects/DateWriter/DateWriter.java b/Code/Projects/DateWriter/DateWriter.java index adde143..d965bbb 100644 --- a/Code/Projects/DateWriter/DateWriter.java +++ b/Code/Projects/DateWriter/DateWriter.java @@ -11,13 +11,14 @@ public class DateWriter { public static void main(String[] args) throws IOException { if (args.length < 3) { - System.out.println("Usage: java /path/to/file/with/timestamps /path/to/new/timestamp/file/with/dates initial_date_in_uuuu-MM-dd_format"); + System.out.println("Usage: java " + DateWriter.class.getSimpleName() + " /path/to/file/with/timestamps /path/to/new/timestamp/file/with/dates initial_date_in_MM/dd/uuuu_format"); System.exit(1); } String pathOriginal = args[0]; String pathModified = args[1]; String initialDateStr = args[2]; - LocalDate date = LocalDate.parse(initialDateStr, DateTimeFormatter.ofPattern("uuuu-MM-dd")); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/uuuu"); + LocalDate date = LocalDate.parse(initialDateStr, dateFormatter); File originalFile = new File(pathOriginal); // Create output file File modifiedFile = new File(pathModified); @@ -31,7 +32,7 @@ public class DateWriter { // Advance date date = date.plusDays(1); } - writer.println(String.format("%s %s", date.toString(), line)); + writer.println(String.format("%s %s", date.format(dateFormatter), line)); prevLine = line; } writer.flush(); diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Main.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Main.java index 4a05501..45fea17 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Main.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Main.java @@ -1,11 +1,9 @@ package edu.uci.iotproject; -import edu.uci.iotproject.analysis.PcapPacketPair; import edu.uci.iotproject.analysis.TcpConversationUtils; import edu.uci.iotproject.analysis.TriggerTrafficExtractor; import edu.uci.iotproject.io.TriggerTimesFileReader; import org.pcap4j.core.*; -import org.pcap4j.packet.IpV4Packet; import org.pcap4j.packet.namednumber.DataLinkType; import java.io.EOFException; @@ -29,100 +27,24 @@ public class Main { public static void main(String[] args) throws PcapNativeException, NotOpenException, EOFException, TimeoutException, UnknownHostException { // ------------------------------------------------------------------------------------------------------------- - // Example/debug code for searching for a pattern at the MAC layer. -// String fileName = "./pcap/mac-tplink.local.pcapng"; -// PcapHandle handle; -// try { -// handle = Pcaps.openOffline(fileName, PcapHandle.TimestampPrecision.NANO); -// } catch (PcapNativeException pne) { -// handle = Pcaps.openOffline(fileName); -// } -// Arrays.asList(1590, 1590, 1590, 1001, 337, 197, 636, 1311, 177) // Full pattern (all non-zero payload packets). -// MacLayerFlowPattern pattern = new MacLayerFlowPattern("TP_LINK_LOCAL_OFF_MAC", "50:c7:bf:33:1f:09", Arrays.asList(637, 1312)); -// MacLayerFlowPatternFinder finder = new MacLayerFlowPatternFinder(handle, pattern); -// finder.findFlowPattern(); - // ------------------------------------------------------------------------------------------------------------- -// -// //final String fileName = args.length > 0 ? args[0] : "/home/rtrimana/pcap_processing/smart_home_traffic/Code/Projects/SmartPlugDetector/pcap/wlan1.local.dns.pcap"; -// final String fileName = args.length > 0 ? args[0] : "/scratch/June-2018/TPLink/wlan1/tplink.wlan1.local.pcap"; -// //final String fileName = args.length > 0 ? args[0] : "/scratch/June-2018/DLink/wlan1/dlink.wlan1.local.pcap"; -// final String trainingFileName = "./pcap/TP_LINK_LOCAL_ON_SUBSET.pcap"; -//// final String trainingFileName = "./pcap/TP_LINK_LOCAL_ON.pcap"; -//// -//// // ====== Debug code ====== -// PcapHandle handle; -// PcapHandle trainingPcap; -// try { -// handle = Pcaps.openOffline(fileName, PcapHandle.TimestampPrecision.NANO); -// trainingPcap = Pcaps.openOffline(trainingFileName, PcapHandle.TimestampPrecision.NANO); -// } catch (PcapNativeException pne) { -// handle = Pcaps.openOffline(fileName); -// trainingPcap = Pcaps.openOffline(trainingFileName); -// } -//// -//// // TODO: The followings are the way to extract multiple hostnames and their associated packet lengths lists -//// //List list = new ArrayList<>(); -//// //list.add("events.tplinkra.com"); -//// //FlowPattern fp = new FlowPattern("TP_LINK_LOCAL_ON", list, trainingPcap); -//// //List list2 = new ArrayList<>(); -//// //list2.add("devs.tplinkcloud.com"); -//// //list2.add("events.tplinkra.com"); -//// //FlowPattern fp3 = new FlowPattern("TP_LINK_REMOTE_ON", list2, trainingPcap); -//// -// FlowPattern fp = new FlowPattern("TP_LINK_LOCAL_ON", "events.tplinkra.com", trainingPcap); -// //FlowPattern fp = new FlowPattern("DLINK_LOCAL_ON", "rfe-us-west-1.dch.dlink.com", trainingPcap); -// FlowPatternFinder fpf = new FlowPatternFinder(handle, fp); -// fpf.start(); -//// -//// // ======================== - - /* - PcapReader pcapReader = new PcapReader(args[0]); - PcapProcessingPipeline pipeline = new PcapProcessingPipeline(pcapReader); - TcpReassembler tcpReassembler = new TcpReassembler(); - pipeline.addPcapPacketConsumer(tcpReassembler); - pipeline.executePipeline(); - System.out.println("Pipeline terminated"); - - List> pairs = new ArrayList<>(); - for (Conversation c : tcpReassembler.getTcpConversations()) { - pairs.add(TcpConversationUtils.extractPacketPairs(c)); - } - */ - - /* - // -------- 07-17-2018 -------- - // Only consider packets to/from the TP-Link plug. - PcapReader pcapReader = new PcapReader(args[0], "ip host 192.168.1.159"); - TcpReassembler tcpReassembler = new TcpReassembler(); - PcapPacket packet; - while((packet = pcapReader.readNextPacket()) != null) { - tcpReassembler.consumePacket(packet); - } - // Now we have a set of reassembled TCP conversations. - List conversations = tcpReassembler.getTcpConversations(); - for(Conversation c : conversations) { - List pairs = TcpConversationUtils.extractPacketPairs(c); - for (PcapPacketPair pair : pairs) { - // TODO ... - // 1. discard packets that are not within X seconds after trigger time - // 2. conversations may be (are) with different servers - so need to plot in different plots, one per hostname? - } - } - - // ---------------------------- - */ + // ------------ # Code for extracting traffic generated by a device within x seconds of a trigger # ------------ + // Paths to input and output files (consider supplying these as arguments instead) and IP of the device for + // which traffic is to be extracted: + // D-Link July 26 experiment + final String inputPcapFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/dlink/dlink.wlan1.local.pcap"; + final String outputPcapFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/dlink/dlink-processed.pcap"; + final String triggerTimesFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/dlink/dlink-july-26-2018.timestamps"; + final String deviceIp = "192.168.1.246"; + // TP-Link July 25 experiment +// final String inputPcapFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/tplink/tplink.wlan1.local.pcap"; +// final String outputPcapFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/tplink/tplink-processed.pcap"; +// final String triggerTimesFile = "/Users/varmarken/temp/UCI IoT Project/experiments/2018-07/tplink/tplink-july-25-2018.timestamps"; +// final String deviceIp = "192.168.1.159"; - // -------- 07-19-2018 -------- TriggerTimesFileReader ttfr = new TriggerTimesFileReader(); -// List triggerTimes = ttfr.readTriggerTimes("/Users/varmarken/Downloads/tplink-feb-13-2018.timestamps", false); - List triggerTimes = ttfr.readTriggerTimes("/Users/varmarken/temp/UCI IoT Project/June2018 experiments/tplink/tplink-june-14-2018-timestamps.txt", false); -// String pcapFile = "/Users/varmarken/Development/Repositories/UCI/NetworkingGroup/smart_home_traffic/Code/Projects/SmartPlugDetector/pcap/wlan1.local.dns.pcap"; - String pcapFile = "/Users/varmarken/temp/UCI IoT Project/June2018 experiments/tplink/tplink.wlan1.local.pcap"; - String tpLinkPlugIp = "192.168.1.159"; - TriggerTrafficExtractor tte = new TriggerTrafficExtractor(pcapFile, triggerTimes, tpLinkPlugIp); -// final PcapDumper outputter = Pcaps.openDead(DataLinkType.EN10MB, 65536).dumpOpen("/Users/varmarken/temp/traces/output/tplink-filtered.pcap"); - final PcapDumper outputter = Pcaps.openDead(DataLinkType.EN10MB, 65536).dumpOpen("/Users/varmarken/temp/UCI IoT Project/June2018 experiments/tplink/tplink-filtered.pcap"); + List triggerTimes = ttfr.readTriggerTimes(triggerTimesFile, false); + TriggerTrafficExtractor tte = new TriggerTrafficExtractor(inputPcapFile, triggerTimes, deviceIp); + final PcapDumper outputter = Pcaps.openDead(DataLinkType.EN10MB, 65536).dumpOpen(outputPcapFile); DnsMap dnsMap = new DnsMap(); TcpReassembler tcpReassembler = new TcpReassembler(); tte.performExtraction(pkt -> { @@ -135,94 +57,26 @@ public class Main { outputter.flush(); outputter.close(); - /* - int packets = 0; - for (Conversation c : tcpReassembler.getTcpConversations()) { - packets += c.getPackets().size(); - packets += c.getSynPackets().size(); - // only count the FIN packets, not the ACKs; every FinAckPair holds a FIN packet - packets += c.getFinAckPairs().size(); - } - // Produces 271 packets for the Feb 13 experiment - // Applying filter: "(tcp and not tcp.len == 0 and not tcp.analysis.retransmission and not tcp.analysis.fast_retransmission) or (tcp.flags.syn == 1) or (tcp.flags.fin == 1)" - // to the file gives 295 packets, but there are 24 TCP-Out-Of-Order SYN/SYNACKs which are filtered as retransmissions in Conversation, so the numbers seem to match. - System.out.println("number of packets: " + packets); - */ - - List> pairs = new ArrayList<>(); - for (Conversation c : tcpReassembler.getTcpConversations()) { - pairs.add(TcpConversationUtils.extractPacketPairs(c)); - } - /* - // Sort pairs according to timestamp of first packet of conversation for (debugging) convenience. - Collections.sort(pairs, (l1, l2) -> { - if (l1.get(0).getFirst().getTimestamp().isBefore(l2.get(0).getFirst().getTimestamp())) return -1; - else if (l2.get(0).getFirst().getTimestamp().isBefore(l1.get(0).getFirst().getTimestamp())) return 1; - else return 0; - }); - */ - System.out.println("list of pairs produced"); - List eventstplinkraPairs = new ArrayList<>(); - List> otherPairs = new ArrayList<>(); - String hostname = "events.tplinkra.com"; - int emptyLists = 0; - for (List lppp : pairs) { - if (lppp.size() < 1) { - emptyLists++; - continue; - } - IpV4Packet ipPacket = lppp.get(0).getFirst().get(IpV4Packet.class); - // If packets are associated with the hostname - if (dnsMap.isRelatedToCloudServer(ipPacket.getHeader().getSrcAddr().getHostAddress(), hostname) || - dnsMap.isRelatedToCloudServer(ipPacket.getHeader().getDstAddr().getHostAddress(), hostname)) { - eventstplinkraPairs.addAll(lppp); - } else { - // Pairs associated with different server - otherPairs.add(lppp); - } - } - System.out.println("number of empty list of packet pairs: " + emptyLists); - HashMap pairCount = new HashMap<>(); - for (PcapPacketPair ppp : eventstplinkraPairs) { - if (pairCount.containsKey(ppp.toString())) { - pairCount.put(ppp.toString(), pairCount.get(ppp.toString()) + 1); - } else { - pairCount.put(ppp.toString(), 1); - } - } - System.out.println("pairCount map built"); - - // Build map containing frequencies of packet lengths exchanged with events.tplinkra.com as well as a map with - // the frequencies of specific sequences of packet lengths for the same hostname - HashMap eventstplinkraPacketLengthFreqMap = new HashMap<>(); - HashMap eventstplinkraPacketSequenceFreqMap = new HashMap<>(); - for (Conversation c : tcpReassembler.getTcpConversations()) { - if (c.getPackets().size() == 0) { - continue; - } - PcapPacket firstPacket = c.getPackets().get(0); - IpV4Packet firstPacketIp = firstPacket.get(IpV4Packet.class); - if (!dnsMap.isRelatedToCloudServer(firstPacketIp.getHeader().getSrcAddr().getHostAddress(), hostname) && - !dnsMap.isRelatedToCloudServer(firstPacketIp.getHeader().getDstAddr().getHostAddress(), hostname)) { - continue; - } - // Update the packet length freq map - for (PcapPacket pp : c.getPackets()) { - eventstplinkraPacketLengthFreqMap.merge(pp.length(), 1, (i1, i2) -> i1 + i2); - } - // Update the packet sequence freq map - StringBuilder sb = new StringBuilder(); - for (PcapPacket pp : c.getPackets()) { - sb.append(pp.length() + " "); - } - eventstplinkraPacketSequenceFreqMap.merge(sb.toString(), 1, (i1, i2) -> i1+i2); - } - System.out.println("packet length frequency map created"); - - Map> hostnameConversationMap = - TcpConversationUtils.groupConversationsByHostname(tcpReassembler.getTcpConversations(), dnsMap); - System.out.println("hostnameConversationMap created"); - // ---------------------------- + // Extract all conversations present in the filtered trace. + List allConversations = tcpReassembler.getTcpConversations(); + // Group conversations by hostname. + Map> convsByHostname = TcpConversationUtils.groupConversationsByHostname(allConversations, dnsMap); + System.out.println("Grouped conversations by hostname."); + // For each hostname, count the frequencies of packet lengths exchanged with that hostname. + final Map> pktLenFreqsByHostname = new HashMap<>(); + convsByHostname.forEach((host, convs) -> pktLenFreqsByHostname.put(host, TcpConversationUtils.countPacketLengthFrequencies(convs))); + System.out.println("Counted frequencies of packet lengths exchanged with each hostname."); + // For each hostname, count the frequencies of packet sequences (i.e., count how many conversations exchange a + // sequence of packets of some specific lengths). + final Map> pktSeqFreqsByHostname = new HashMap<>(); + convsByHostname.forEach((host, convs) -> pktSeqFreqsByHostname.put(host, TcpConversationUtils.countPacketSequenceFrequencies(convs))); + System.out.println("Counted frequencies of packet sequences exchanged with each hostname."); + // For each hostname, count frequencies of packet pairs exchanged with that hostname across all conversations + final Map> pktPairFreqsByHostname = + TcpConversationUtils.countPacketPairFrequenciesByHostname(allConversations, dnsMap); + System.out.println("Counted frequencies of packet pairs per hostname"); + // ------------------------------------------------------------------------------------------------------------- + // ------------------------------------------------------------------------------------------------------------- } } diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TcpConversationUtils.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TcpConversationUtils.java index 2f5f415..427d890 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TcpConversationUtils.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TcpConversationUtils.java @@ -10,12 +10,26 @@ import org.pcap4j.packet.TcpPacket; import java.util.*; /** - * TODO add class documentation. + * Utility functions for analyzing and structuring (sets of) {@link Conversation}s. * - * @author Janus Varmarken + * @author Janus Varmarken {@literal } + * @author Rahmadi Trimananda {@literal } */ public class TcpConversationUtils { + + /** + *

+ * Given a {@link Conversation}, extract its set of "packet pairs", i.e., pairs of request-reply packets. + *

+ * + * Note: in the current implementation, if one endpoint sends multiple packets back-to-back with no + * interleaved reply packets from the other endpoint, such packets are converted to one-item pairs (i.e., instances + * of {@lin PcapPacketPair} where {@link PcapPacketPair#getSecond()} is {@code null}). + * + * @param conv The {@code Conversation} for which packet pairs are to be extracted. + * @return The packet pairs extracted from {@code conv}. + */ public static List extractPacketPairs(Conversation conv) { List packets = conv.getPackets(); List pairs = new ArrayList<>(); @@ -48,8 +62,14 @@ public class TcpConversationUtils { // TODO: what if there is long time between response and reply packet? Should we add a threshold and exclude those cases? } - - public static Map> groupConversationsByHostname(List tcpConversations, DnsMap ipHostnameMappings) { + /** + * Given a collection of TCP conversations and associated DNS mappings, groups the conversations by hostname. + * @param tcpConversations The collection of TCP conversations. + * @param ipHostnameMappings The associated DNS mappings. + * @return A map where each key is a hostname and its associated value is a list of conversations where one of the + * two communicating hosts is that hostname (i.e. its IP maps to the hostname). + */ + public static Map> groupConversationsByHostname(Collection tcpConversations, DnsMap ipHostnameMappings) { HashMap> result = new HashMap<>(); for (Conversation c : tcpConversations) { if (c.getPackets().size() == 0) { @@ -96,5 +116,74 @@ public class TcpConversationUtils { return result; } + public static Map countPacketSequenceFrequencies(Collection conversations) { + Map result = new HashMap<>(); + for (Conversation conv : conversations) { + if (conv.getPackets().size() == 0) { + // Skip conversations with no payload packets. + continue; + } + StringBuilder sb = new StringBuilder(); + for (PcapPacket pp : conv.getPackets()) { + sb.append(pp.length() + " "); + } + result.merge(sb.toString(), 1, (i1, i2) -> i1+i2); + } + return result; + } + + /** + * Given a {@link Conversation}, counts the frequencies of each unique packet length seen as part of the + * {@code Conversation}. + * @param c The {@code Conversation} for which unique packet length frequencies are to be determined. + * @return A mapping from packet length to its frequency. + */ + public static Map countPacketLengthFrequencies(Conversation c) { + Map result = new HashMap<>(); + for (PcapPacket packet : c.getPackets()) { + result.merge(packet.length(), 1, (i1, i2) -> i1 + i2); + } + return result; + } + + /** + * Like {@link #countPacketLengthFrequencies(Conversation)}, but counts packet length frequencies for a collection + * of {@code Conversation}s, i.e., the frequency of a packet length becomes the total number of packets with that + * length across all {@code Conversation}s in {@code conversations}. + * @param conversations The collection of {@code Conversation}s for which packet length frequencies are to be + * counted. + * @return A mapping from packet length to its frequency. + */ + public static Map countPacketLengthFrequencies(Collection conversations) { + Map result = new HashMap<>(); + for (Conversation c : conversations) { + Map intermediateResult = countPacketLengthFrequencies(c); + for (Map.Entry entry : intermediateResult.entrySet()) { + result.merge(entry.getKey(), entry.getValue(), (i1, i2) -> i1 + i2); + } + } + return result; + } + + public static Map countPacketPairFrequencies(Collection pairs) { + Map result = new HashMap<>(); + for (PcapPacketPair ppp : pairs) { + result.merge(ppp.toString(), 1, (i1, i2) -> i1 + i2); + } + return result; + } + public static Map> countPacketPairFrequenciesByHostname(Collection tcpConversations, DnsMap ipHostnameMappings) { + Map> convsByHostname = groupConversationsByHostname(tcpConversations, ipHostnameMappings); + HashMap> result = new HashMap<>(); + for (Map.Entry> entry : convsByHostname.entrySet()) { + // Merge all packet pairs exchanged during the course of all conversations with hostname into one list + List allPairsExchangedWithHostname = new ArrayList<>(); + entry.getValue().forEach(conversation -> allPairsExchangedWithHostname.addAll(extractPacketPairs(conversation))); + // Then count the frequencies of packet pairs exchanged with the hostname, irrespective of individual + // conversations + result.put(entry.getKey(), countPacketPairFrequencies(allPairsExchangedWithHostname)); + } + return result; + } } diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TriggerTrafficExtractor.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TriggerTrafficExtractor.java index c0b553c..26276b1 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TriggerTrafficExtractor.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/analysis/TriggerTrafficExtractor.java @@ -22,7 +22,7 @@ public class TriggerTrafficExtractor implements PcapPacketFilter { private int mTriggerIndex = 0; - private static final int INCLUSION_WINDOW_MILLIS = 3_000; + private static final int INCLUSION_WINDOW_MILLIS = 20_000; public TriggerTrafficExtractor(String pcapFilePath, List triggerTimes, String deviceIp) throws PcapNativeException, NotOpenException { mPcapFilePath = pcapFilePath; @@ -55,6 +55,15 @@ public class TriggerTrafficExtractor implements PcapPacketFilter { @Override public boolean shouldIncludePacket(PcapPacket packet) { + // New version. Simpler, but slower: the later a packet arrives, the more elements of mTriggerTimes will need to + // be traversed. + return mTriggerTimes.stream().anyMatch( + trigger -> trigger.isBefore(packet.getTimestamp()) && + packet.getTimestamp().isBefore(trigger.plusMillis(INCLUSION_WINDOW_MILLIS)) + ); + + /* + // Old version. Faster, but more complex - is it correct? if (mTriggerIndex >= mTriggerTimes.size()) { // Don't include packet if we've exhausted the list of trigger times. return false; @@ -78,6 +87,7 @@ public class TriggerTrafficExtractor implements PcapPacketFilter { return shouldIncludePacket(packet); } } + */ } } diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/io/TriggerTimesFileReader.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/io/TriggerTimesFileReader.java index 592bcd7..75d06ec 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/io/TriggerTimesFileReader.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/io/TriggerTimesFileReader.java @@ -32,9 +32,8 @@ public class TriggerTimesFileReader { */ public List readTriggerTimes(String fileName, boolean _24hFormat) { List listTriggerTimes = new ArrayList<>(); - try { - File file = new File(fileName); - BufferedReader br = new BufferedReader(new FileReader(file)); + File file = new File(fileName); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { String s; while ((s = br.readLine()) != null) { listTriggerTimes.add(parseTriggerTimestamp(s, _24hFormat)); @@ -48,7 +47,7 @@ public class TriggerTimesFileReader { /** * Parses a timestamp string to an {@link Instant} (UTC). Assumes timestamps are LA time. - * Format is expected to be either "uuuu-MM-dd HH:mm:ss" or "uuuu-MM-dd h:mm:ss a". + * Format is expected to be either "MM/dd/uuuu HH:mm:ss" or "MM/dd/uuuu h:mm:ss a". * * @param timestampStr The string containing a date-time timestamp for LA's timezone. * @param _24hFormat {@code true} if the time in {@code timestampStr} is given in 24 hour format, {@code false} if @@ -59,7 +58,7 @@ public class TriggerTimesFileReader { */ public Instant parseTriggerTimestamp(String timestampStr, boolean _24hFormat) { // Note: only one 'h' when not prefixed with leading 0 for 1-9; and only one 'a' for AM/PM marker in Java 8 time - String format = _24hFormat ? "uuuu-MM-dd HH:mm:ss" : "uuuu-MM-dd h:mm:ss a"; + String format = _24hFormat ? "MM/dd/uuuu HH:mm:ss" : "MM/dd/uuuu h:mm:ss a"; LocalDateTime localDateTime = LocalDateTime.parse(timestampStr, DateTimeFormatter.ofPattern(format, Locale.US)); ZonedDateTime laZonedDateTime = localDateTime.atZone(ZONE_ID_LOS_ANGELES); return laZonedDateTime.toInstant();