1 package edu.uci.iotproject;
3 import org.pcap4j.core.PcapPacket;
4 import org.pcap4j.packet.IpV4Packet;
5 import org.pcap4j.packet.TcpPacket;
10 * Models a (TCP) conversation/connection/session/flow (packet's belonging to the same session between a client and a
12 * Holds a list of {@link PcapPacket}s identified as pertaining to the flow. Note that this list is <em>not</em>
13 * considered when determining equality of two {@code Conversation} instances in order to allow for a
14 * {@code Conversation} to function as a key in data structures such as {@link java.util.Map} and {@link java.util.Set}.
15 * See {@link #equals(Object)} for the definition of equality.
17 * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
18 * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
20 public class Conversation {
22 /* Begin instance properties */
24 * The IP of the host that is considered the client (i.e. the host that initiates the conversation)
25 * in this conversation.
27 private final String mClientIp;
30 * The port number used by the host that is considered the client in this conversation.
32 private final int mClientPort;
35 * The IP of the host that is considered the server (i.e. is the responder) in this conversation.
37 private final String mServerIp;
40 * The port number used by the server in this conversation.
42 private final int mServerPort;
45 * The list of packets pertaining to this conversation.
47 private final List<PcapPacket> mPackets;
50 * Contains the sequence numbers seen so far for this {@code Conversation}.
51 * Used for filtering out retransmissions.
53 private final Set<Integer> mSeqNumbers;
56 * List of pairs FINs and their corresponding ACKs associated with this conversation.
58 private List<FinAckPair> mFinPackets;
59 /* End instance properties */
62 * Constructs a new {@code Conversation}.
63 * @param clientIp The IP of the host that is considered the client (i.e. the host that initiates the conversation)
64 * in the conversation.
65 * @param clientPort The port number used by the client for the conversation.
66 * @param serverIp The IP of the host that is considered the server (i.e. is the responder) in the conversation.
67 * @param serverPort The port number used by the server for the conversation.
69 public Conversation(String clientIp, int clientPort, String serverIp, int serverPort) {
70 this.mClientIp = clientIp;
71 this.mClientPort = clientPort;
72 this.mServerIp = serverIp;
73 this.mServerPort = serverPort;
74 this.mPackets = new ArrayList<>();
75 this.mSeqNumbers = new HashSet<>();
76 this.mFinPackets = new ArrayList<>();
80 * Add a packet to the list of packets associated with this conversation.
81 * @param packet The packet that is to be added to (associated with) this conversation.
82 * @param ignoreRetransmissions Boolean value indicating if retransmissions should be ignored.
83 * If set to {@code true}, {@code packet} will <em>not</em> be added to the
84 * internal list of packets pertaining to this {@code Conversation}
85 * <em>iff</em> the sequence number of {@code packet} was already
86 * seen in a previous packet.
88 public void addPacket(PcapPacket packet, boolean ignoreRetransmissions) {
89 // Precondition: verify that packet does indeed pertain to conversation.
90 onAddPrecondition(packet);
91 // For now we only support TCP flows.
92 TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
93 int seqNo = tcpPacket.getHeader().getSequenceNumber();
94 if (ignoreRetransmissions && mSeqNumbers.contains(seqNo)) {
95 // Packet is a retransmission. Ignore it.
98 // Update set of sequence numbers seen so far with sequence number of new packet.
99 mSeqNumbers.add(seqNo);
100 // Finally add packet to list of packets pertaining to this conversation.
101 mPackets.add(packet);
105 * Adds a TCP FIN packet to the list of TCP FIN packets associated with this conversation.
106 * @param finPacket The TCP FIN packet that is to be added to (associated with) this conversation.
108 public void addFinPacket(PcapPacket finPacket) {
109 // Precondition: verify that packet does indeed pertain to conversation.
110 onAddPrecondition(finPacket);
111 mFinPackets.add(new FinAckPair(finPacket));
115 * Attempt to ACK any FIN packets held by this conversation.
116 * @param ackPacket The ACK for a FIN previously added to this conversation.
118 public void attemptAcknowledgementOfFin(PcapPacket ackPacket) {
119 // Precondition: verify that the packet pertains to this conversation.
120 onAddPrecondition(ackPacket);
121 // Mark unack'ed FIN(s) that this ACK matches as ACK'ed (there might be more than one in case of retransmissions..?)
122 mFinPackets.replaceAll(finAckPair -> (!finAckPair.isAcknowledged() && finAckPair.isCorrespondingAckPacket(ackPacket)) ? new FinAckPair(finAckPair.getFinPacket(), ackPacket) : finAckPair);
126 * Get a list of packets pertaining to this {@code Conversation}.
127 * The returned list is a read-only list.
128 * @return the list of packets pertaining to this {@code Conversation}.
130 public List<PcapPacket> getPackets() {
131 // Return read-only view to prevent external code from manipulating internal state (preserve invariant).
132 return Collections.unmodifiableList(mPackets);
135 // =========================================================================================================
136 // We simply reuse equals and hashCode methods of String.class to be able to use this class as a key
140 * <em>Note:</em> currently, equality is determined based on pairwise equality of the elements of the four tuple
141 * ({@link #mClientIp}, {@link #mClientPort}, {@link #mServerIp}, {@link #mServerPort}) for {@code this} and
143 * @param obj The object to test for equality with {@code this}.
144 * @return {@code true} if {@code obj} is considered equal to {@code this} based on the definition of equality given above.
147 public boolean equals(Object obj) {
148 return obj instanceof Conversation && this.toString().equals(obj.toString());
152 public int hashCode() {
153 return toString().hashCode();
155 // =========================================================================================================
158 public String toString() {
159 return String.format("%s:%d %s:%d", mClientIp, mClientPort, mServerIp, mServerPort);
163 * Invoke to verify that the precondition holds when a caller attempts to add a packet to this {@code Conversation}.
164 * An {@link IllegalArgumentException} is thrown if the precondition is violated.
165 * @param packet the packet to be added to this {@code Conversation}
167 private void onAddPrecondition(PcapPacket packet) {
168 // Apply precondition to preserve class invariant: all packets in mPackets must match the 4 tuple that
169 // defines the conversation.
170 IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
171 // For now we only support TCP flows.
172 TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
173 String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
174 String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
175 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
176 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
177 String clientIp, serverIp;
178 int clientPort, serverPort;
179 if (ipSrc.equals(mClientIp)) {
181 clientPort = srcPort;
183 serverPort = dstPort;
186 clientPort = dstPort;
188 serverPort = srcPort;
190 if (!(clientIp.equals(mClientIp) && clientPort == mClientPort &&
191 serverIp.equals(mServerIp) && serverPort == mServerPort)) {
192 throw new IllegalArgumentException(
193 String.format("Attempt to add packet that does not pertain to %s",
194 Conversation.class.getSimpleName()));