+ /**
+ * Get a list of packets pertaining to this {@code Conversation}.
+ * The returned list is a read-only list.
+ * @return the list of packets pertaining to this {@code Conversation}.
+ */
+ public List<PcapPacket> getPackets() {
+ // Return read-only view to prevent external code from manipulating internal state (preserve invariant).
+ return Collections.unmodifiableList(mPackets);
+ }
+
+ /**
+ * Adds a TCP FIN packet to the list of TCP FIN packets associated with this conversation.
+ * @param finPacket The TCP FIN packet that is to be added to (associated with) this conversation.
+ */
+ public void addFinPacket(PcapPacket finPacket) {
+ // Precondition: verify that packet does indeed pertain to conversation.
+ onAddPrecondition(finPacket);
+ mFinPackets.add(new FinAckPair(finPacket));
+ }
+
+ /**
+ * Attempt to ACK any FIN packets held by this conversation.
+ * @param ackPacket The ACK for a FIN previously added to this conversation.
+ */
+ public void attemptAcknowledgementOfFin(PcapPacket ackPacket) {
+ // Precondition: verify that the packet pertains to this conversation.
+ onAddPrecondition(ackPacket);
+ // Mark unack'ed FIN(s) that this ACK matches as ACK'ed (there might be more than one in case of retransmissions..?)
+ mFinPackets.replaceAll(finAckPair -> (!finAckPair.isAcknowledged() && finAckPair.isCorrespondingAckPacket(ackPacket)) ? new FinAckPair(finAckPair.getFinPacket(), ackPacket) : finAckPair);
+ }
+
+ /**
+ * Get if this {@code Conversation} is considered to have been gracefully shut down.
+ * A {@code Conversation} has been gracefully shut down if it contains a FIN+ACK pair for both directions
+ * (client to server, and server to client).
+ * @return {@code true} if the connection has been gracefully shut down, false otherwise.
+ */
+ public boolean isGracefullyShutdown() {
+ // 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.
+ return mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mClientIp, mClientPort)) &&
+ mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mServerIp, mServerPort));
+ }
+