1 package edu.uci.iotproject;
3 import edu.uci.iotproject.util.PcapPacketUtils;
4 import org.pcap4j.core.PcapPacket;
5 import org.pcap4j.packet.IpV4Packet;
6 import org.pcap4j.packet.TcpPacket;
11 * Models a (TCP) conversation/connection/session/flow (packet's belonging to the same session between a client and a
13 * Holds a list of {@link PcapPacket}s identified as pertaining to the flow. Note that this list is <em>not</em>
14 * considered when determining equality of two {@code Conversation} instances in order to allow for a
15 * {@code Conversation} to function as a key in data structures such as {@link java.util.Map} and {@link java.util.Set}.
16 * See {@link #equals(Object)} for the definition of equality.
18 * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
19 * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
21 public class Conversation {
23 /* Begin instance properties */
25 * The IP of the host that is considered the client (i.e. the host that initiates the conversation)
26 * in this conversation.
28 private final String mClientIp;
31 * The port number used by the host that is considered the client in this conversation.
33 private final int mClientPort;
36 * The IP of the host that is considered the server (i.e. is the responder) in this conversation.
38 private final String mServerIp;
41 * The port number used by the server in this conversation.
43 private final int mServerPort;
46 * The list of packets pertaining to this conversation.
48 private final List<PcapPacket> mPackets;
51 * Contains the sequence numbers seen so far for this {@code Conversation}.
52 * Used for filtering out retransmissions.
54 private final Set<Integer> mSeqNumbers;
57 * List of pairs FINs and their corresponding ACKs associated with this conversation.
59 private List<FinAckPair> mFinPackets;
60 /* End instance properties */
63 * Constructs a new {@code Conversation}.
64 * @param clientIp The IP of the host that is considered the client (i.e. the host that initiates the conversation)
65 * in the conversation.
66 * @param clientPort The port number used by the client for the conversation.
67 * @param serverIp The IP of the host that is considered the server (i.e. is the responder) in the conversation.
68 * @param serverPort The port number used by the server for the conversation.
70 public Conversation(String clientIp, int clientPort, String serverIp, int serverPort) {
71 this.mClientIp = clientIp;
72 this.mClientPort = clientPort;
73 this.mServerIp = serverIp;
74 this.mServerPort = serverPort;
75 this.mPackets = new ArrayList<>();
76 this.mSeqNumbers = new HashSet<>();
77 this.mFinPackets = new ArrayList<>();
81 * Add a packet to the list of packets associated with this conversation.
82 * @param packet The packet that is to be added to (associated with) this conversation.
83 * @param ignoreRetransmissions Boolean value indicating if retransmissions should be ignored.
84 * If set to {@code true}, {@code packet} will <em>not</em> be added to the
85 * internal list of packets pertaining to this {@code Conversation}
86 * <em>iff</em> the sequence number of {@code packet} was already
87 * seen in a previous packet.
89 public void addPacket(PcapPacket packet, boolean ignoreRetransmissions) {
90 // Precondition: verify that packet does indeed pertain to conversation.
91 onAddPrecondition(packet);
92 // For now we only support TCP flows.
93 TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
94 int seqNo = tcpPacket.getHeader().getSequenceNumber();
95 if (ignoreRetransmissions && mSeqNumbers.contains(seqNo)) {
96 // Packet is a retransmission. Ignore it.
99 // Update set of sequence numbers seen so far with sequence number of new packet.
100 mSeqNumbers.add(seqNo);
101 // Finally add packet to list of packets pertaining to this conversation.
102 mPackets.add(packet);
106 * Get a list of packets pertaining to this {@code Conversation}.
107 * The returned list is a read-only list.
108 * @return the list of packets pertaining to this {@code Conversation}.
110 public List<PcapPacket> getPackets() {
111 // Return read-only view to prevent external code from manipulating internal state (preserve invariant).
112 return Collections.unmodifiableList(mPackets);
116 * Adds a TCP FIN packet to the list of TCP FIN packets associated with this conversation.
117 * @param finPacket The TCP FIN packet that is to be added to (associated with) this conversation.
119 public void addFinPacket(PcapPacket finPacket) {
120 // Precondition: verify that packet does indeed pertain to conversation.
121 onAddPrecondition(finPacket);
122 mFinPackets.add(new FinAckPair(finPacket));
126 * Attempt to ACK any FIN packets held by this conversation.
127 * @param ackPacket The ACK for a FIN previously added to this conversation.
129 public void attemptAcknowledgementOfFin(PcapPacket ackPacket) {
130 // Precondition: verify that the packet pertains to this conversation.
131 onAddPrecondition(ackPacket);
132 // Mark unack'ed FIN(s) that this ACK matches as ACK'ed (there might be more than one in case of retransmissions..?)
133 mFinPackets.replaceAll(finAckPair -> !finAckPair.isAcknowledged() && finAckPair.isCorrespondingAckPacket(ackPacket) ? new FinAckPair(finAckPair.getFinPacket(), ackPacket) : finAckPair);
137 * Retrieves an unmodifiable view of the list of {@link FinAckPair}s associated with this {@code Conversation}.
138 * @return an unmodifiable view of the list of {@link FinAckPair}s associated with this {@code Conversation}.
140 public List<FinAckPair> getFinAckPairs() {
141 return Collections.unmodifiableList(mFinPackets);
145 * Get if this {@code Conversation} is considered to have been gracefully shut down.
146 * A {@code Conversation} has been gracefully shut down if it contains a FIN+ACK pair for both directions
147 * (client to server, and server to client).
148 * @return {@code true} if the connection has been gracefully shut down, false otherwise.
150 public boolean isGracefullyShutdown() {
151 // The conversation has been gracefully shut down if we have recorded a FIN from both the client and the server which have both been ack'ed.
152 return mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mClientIp, mClientPort)) &&
153 mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mServerIp, mServerPort));
156 // =========================================================================================================
157 // We simply reuse equals and hashCode methods of String.class to be able to use this class as a key
161 * <em>Note:</em> currently, equality is determined based on pairwise equality of the elements of the four tuple
162 * ({@link #mClientIp}, {@link #mClientPort}, {@link #mServerIp}, {@link #mServerPort}) for {@code this} and
164 * @param obj The object to test for equality with {@code this}.
165 * @return {@code true} if {@code obj} is considered equal to {@code this} based on the definition of equality given above.
168 public boolean equals(Object obj) {
169 return obj instanceof Conversation && this.toString().equals(obj.toString());
173 public int hashCode() {
174 return toString().hashCode();
176 // =========================================================================================================
179 public String toString() {
180 return String.format("%s:%d %s:%d", mClientIp, mClientPort, mServerIp, mServerPort);
184 * Invoke to verify that the precondition holds when a caller attempts to add a packet to this {@code Conversation}.
185 * An {@link IllegalArgumentException} is thrown if the precondition is violated.
186 * @param packet the packet to be added to this {@code Conversation}
188 private void onAddPrecondition(PcapPacket packet) {
189 // Apply precondition to preserve class invariant: all packets in mPackets must match the 4 tuple that
190 // defines the conversation.
191 IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
192 // For now we only support TCP flows.
193 TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
194 String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
195 String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
196 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
197 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
198 String clientIp, serverIp;
199 int clientPort, serverPort;
200 if (ipSrc.equals(mClientIp)) {
202 clientPort = srcPort;
204 serverPort = dstPort;
207 clientPort = dstPort;
209 serverPort = srcPort;
211 if (!(clientIp.equals(mClientIp) && clientPort == mClientPort &&
212 serverIp.equals(mServerIp) && serverPort == mServerPort)) {
213 throw new IllegalArgumentException(
214 String.format("Attempt to add packet that does not pertain to %s",
215 Conversation.class.getSimpleName()));