8d5780ed0b890dc9240992589b9ea57112429d9b
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / Conversation.java
1 package edu.uci.iotproject;
2
3 import org.pcap4j.core.PcapPacket;
4 import org.pcap4j.packet.IpV4Packet;
5 import org.pcap4j.packet.TcpPacket;
6
7 import java.util.*;
8
9 /**
10  * Models a (TCP) conversation/connection/session/flow (packet's belonging to the same session between a client and a
11  * server).
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.
16  *
17  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
18  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
19  */
20 public class Conversation {
21
22     /* Begin instance properties */
23     /**
24      * The IP of the host that is considered the client (i.e. the host that initiates the conversation)
25      * in this conversation.
26      */
27     private final String mClientIp;
28
29     /**
30      * The port number used by the host that is considered the client in this conversation.
31      */
32     private final int mClientPort;
33
34     /**
35      * The IP of the host that is considered the server (i.e. is the responder) in this conversation.
36      */
37     private final String mServerIp;
38
39     /**
40      * The port number used by the server in this conversation.
41      */
42     private final int mServerPort;
43
44     /**
45      * The list of packets pertaining to this conversation.
46      */
47     private final List<PcapPacket> mPackets;
48
49     /**
50      * Contains the sequence numbers seen so far for this {@code Conversation}.
51      * Used for filtering out retransmissions.
52      */
53     private final Set<Integer> mSeqNumbers;
54
55     /**
56      * List of pairs FINs and their corresponding ACKs associated with this conversation.
57      */
58     private List<FinAckPair> mFinPackets;
59     /* End instance properties */
60
61     /**
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.
68      */
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<>();
77     }
78
79     /**
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.
87      */
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.
96             return;
97         }
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);
102     }
103
104     /**
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.
107      */
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));
112     }
113
114     /**
115      * Attempt to ACK any FIN packets held by this conversation.
116      * @param ackPacket The ACK for a FIN previously added to this conversation.
117      */
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);
123     }
124
125     /**
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}.
129      */
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);
133     }
134
135     // =========================================================================================================
136     // We simply reuse equals and hashCode methods of String.class to be able to use this class as a key
137     // in a Map.
138
139     /**
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
142      * {@code obj}.
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.
145      */
146     @Override
147     public boolean equals(Object obj) {
148         return obj instanceof Conversation && this.toString().equals(obj.toString());
149     }
150
151     @Override
152     public int hashCode() {
153         return toString().hashCode();
154     }
155     // =========================================================================================================
156
157     @Override
158     public String toString() {
159         return String.format("%s:%d %s:%d", mClientIp, mClientPort, mServerIp, mServerPort);
160     }
161
162     /**
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}
166      */
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)) {
180             clientIp = ipSrc;
181             clientPort = srcPort;
182             serverIp = ipDst;
183             serverPort = dstPort;
184         } else {
185             clientIp = ipDst;
186             clientPort = dstPort;
187             serverIp = ipSrc;
188             serverPort = srcPort;
189         }
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()));
195         }
196     }
197
198 }