+ /**
+ * Records a TCP SYN packet as pertaining to this conversation (adds it to the the internal list).
+ * Attempts to add duplicate SYN packets will be ignored, and the caller is made aware of the attempt to add a
+ * duplicate by the return value being {@code false}.
+ *
+ * @param synPacket A {@link PcapPacket} wrapping a TCP SYN packet.
+ * @return {@code true} if the packet was successfully added to this {@code Conversation}, {@code false} otherwise.
+ */
+ public boolean addSynPacket(PcapPacket synPacket) {
+ onAddPrecondition(synPacket);
+ final IpV4Packet synPacketIpSection = synPacket.get(IpV4Packet.class);
+ final TcpPacket synPacketTcpSection = synPacket.get(TcpPacket.class);
+ if (synPacketTcpSection == null || !synPacketTcpSection.getHeader().getSyn()) {
+ throw new IllegalArgumentException("Not a SYN packet.");
+ }
+ // We are only interested in recording one copy of the two SYN packets (one SYN packet in each direction), i.e.,
+ // we want to discard retransmitted SYN packets.
+ if (mSynPackets.size() >= 2) {
+ return false;
+ }
+ // Check the set of recorded SYN packets to see if we have already recorded a SYN packet going in the same
+ // direction as the packet given in the argument.
+ boolean matchingPrevSyn = mSynPackets.stream().anyMatch(p -> {
+ IpV4Packet pIp = p.get(IpV4Packet.class);
+ TcpPacket pTcp = p.get(TcpPacket.class);
+ boolean srcAddrMatch = synPacketIpSection.getHeader().getSrcAddr().getHostAddress().
+ equals(pIp.getHeader().getSrcAddr().getHostAddress());
+ boolean dstAddrMatch = synPacketIpSection.getHeader().getDstAddr().getHostAddress().
+ equals(pIp.getHeader().getDstAddr().getHostAddress());
+ boolean srcPortMatch = synPacketTcpSection.getHeader().getSrcPort().valueAsInt() ==
+ pTcp.getHeader().getSrcPort().valueAsInt();
+ boolean dstPortMatch = synPacketTcpSection.getHeader().getDstPort().valueAsInt() ==
+ pTcp.getHeader().getDstPort().valueAsInt();
+ return srcAddrMatch && dstAddrMatch && srcPortMatch && dstPortMatch;
+ });
+ if (matchingPrevSyn) {
+ return false;
+ }
+ // Update direction-dependent set of sequence numbers and record/log packet.
+ addSeqNumber(synPacket);
+ return mSynPackets.add(synPacket);
+
+ /*
+ mSynPackets.stream().anyMatch(p -> {
+ IpV4Packet pIp = p.get(IpV4Packet.class);
+ TcpPacket pTcp = p.get(TcpPacket.class);
+ boolean srcAddrMatch = synPacketIpSection.getHeader().getSrcAddr().getHostAddress().
+ equals(pIp.getHeader().getSrcAddr().getHostAddress());
+ boolean dstAddrMatch = synPacketIpSection.getHeader().getDstAddr().getHostAddress().
+ equals(pIp.getHeader().getDstAddr().getHostAddress());
+ boolean srcPortMatch = synPacketTcpSection.getHeader().getSrcPort().valueAsInt() ==
+ pTcp.getHeader().getSrcPort().valueAsInt();
+ boolean dstPortMatch = synPacketTcpSection.getHeader().getDstPort().value() ==
+ pTcp.getHeader().getDstPort().value();
+
+ boolean fourTupleMatch = srcAddrMatch && dstAddrMatch && srcPortMatch && dstPortMatch;
+
+ boolean seqNoMatch = synPacketTcpSection.getHeader().getSequenceNumber() ==
+ pTcp.getHeader().getSequenceNumber();
+
+ if (fourTupleMatch && !seqNoMatch) {
+ // If the four tuple that identifies the conversation matches, but the sequence number is different,
+ // it means that this SYN packet is, in fact, an attempt to establish a **new** connection, and hence
+ // the given packet is NOT part of this conversation, even though the ip:port combinations are (by
+ // chance) selected such that they match this conversation.
+ throw new IllegalArgumentException("Attempt to add SYN packet that belongs to a different conversation " +
+ "(which is identified by the same four tuple as this conversation)");
+ }
+ return fourTupleMatch && seqNoMatch;
+ });
+ */
+ }
+
+ /**
+ * Get a list of SYN packets pertaining to this {@code Conversation}.
+ * The returned list is a read-only list.
+ * @return the list of SYN packets pertaining to this {@code Conversation}.
+ */
+ public List<PcapPacket> getSynPackets() {
+ return Collections.unmodifiableList(mSynPackets);
+ }
+
+ /**
+ * 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);
+ // TODO: should call addSeqNumber here?
+ addSeqNumber(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);
+ }
+
+ /**
+ * Retrieves an unmodifiable view of the list of {@link FinAckPair}s associated with this {@code Conversation}.
+ * @return an unmodifiable view of the list of {@link FinAckPair}s associated with this {@code Conversation}.
+ */
+ public List<FinAckPair> getFinAckPairs() {
+ return Collections.unmodifiableList(mFinPackets);
+ }
+
+ /**
+ * 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));
+ }
+