add method for checking if a Conversation has been gracefully shut down.
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / FinAckPair.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 /**
8  * Groups a FIN packet and its corresponding ACK packet. <b>Immutable and thread safe</b>.
9  *
10  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
11  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
12  */
13 public class FinAckPair {
14
15     private final PcapPacket mFinPacket;
16     private final PcapPacket mCorrespondingAckPacket;
17
18     /**
19      * Constructs a {@code FinAckPair} given a FIN packet.
20      * The corresponding ACK packet field is set to {@code null}.
21      * @param finPacket A FIN packet.
22      */
23     public FinAckPair(PcapPacket finPacket) {
24         if (!finPacket.get(TcpPacket.class).getHeader().getFin()) {
25             throw new IllegalArgumentException("not a FIN packet");
26         }
27         mFinPacket = finPacket;
28         mCorrespondingAckPacket = null;
29     }
30
31     /**
32      * Constructs a {@code FinAckPair} given a FIN and an ACK packet.
33      * @param finPacket A FIN packet.
34      * @param correspondingAckPacket The ACK packet corresponding to {@code finPacket}.
35      */
36     public FinAckPair(PcapPacket finPacket, PcapPacket correspondingAckPacket) {
37         // Enforce class invariant, i.e. that the FIN and ACK are related.
38         // Note that it is indirectly checked whether finPacket is indeed a FIN packet
39         // as isCorrespondingAckPacket calls the single parameter constructor.
40         if (!FinAckPair.isCorrespondingAckPacket(finPacket, correspondingAckPacket)) {
41             throw new IllegalArgumentException("FIN and ACK not related");
42         }
43         mFinPacket = finPacket;
44         mCorrespondingAckPacket = correspondingAckPacket;
45     }
46
47     /**
48      * Get the FIN packet of this pair.
49      * @return the FIN packet of this pair.
50      */
51     public PcapPacket getFinPacket() {
52         return mFinPacket;
53     }
54
55     /**
56      * Get the corresponding ACK packet of this pair, if any.
57      * @return the corresponding ACK packet of this pair, if any.
58      */
59     public PcapPacket getCorrespondingAckPacket() {
60         return mCorrespondingAckPacket;
61     }
62
63     /**
64      * Was the FIN in this {@code FinAckPair} acknowledged?
65      *
66      * @return {@code true} if the corresponding ACK has been set in this {@code FinAckPair}.
67      */
68     public boolean isAcknowledged() {
69         return mFinPacket != null && mCorrespondingAckPacket != null;
70     }
71
72     /**
73      * Checks if a given packet is an ACK corresponding to the FIN packet in this {@code FinAckPair}.
74      * @return {@code true} if {@code packet} is an ACK that corresponds to the FIN in this pair, {@code false} otherwise.
75      */
76     public boolean isCorrespondingAckPacket(PcapPacket packet) {
77         IpV4Packet inputIpPacket = packet.get(IpV4Packet.class);
78         TcpPacket inputTcpPacket = packet.get(TcpPacket.class);
79         if (inputIpPacket == null || inputTcpPacket == null || !inputTcpPacket.getHeader().getAck()) {
80             return false;
81         }
82
83         IpV4Packet finIpPacket = mFinPacket.get(IpV4Packet.class);
84         TcpPacket finTcpPacket = mFinPacket.get(TcpPacket.class);
85
86         // Extract (srcIp:port,dstIp:port) for input and member (FIN) packets.
87         String inputPacketIpSrc = inputIpPacket.getHeader().getSrcAddr().getHostAddress();
88         String inputPacketIpDst = inputIpPacket.getHeader().getDstAddr().getHostAddress();
89         int inputPacketPortSrc = inputTcpPacket.getHeader().getSrcPort().valueAsInt();
90         int inputPacketPortDst = inputTcpPacket.getHeader().getDstPort().valueAsInt();
91         String finPacketIpSrc = finIpPacket.getHeader().getSrcAddr().getHostAddress();
92         String finPacketIpDst = finIpPacket.getHeader().getDstAddr().getHostAddress();
93         int finPacketPortSrc = finTcpPacket.getHeader().getSrcPort().valueAsInt();
94         int finPacketPortDst = finTcpPacket.getHeader().getDstPort().valueAsInt();
95
96         // For the two packets to be related, the dst of one must be the src of the other.
97         // Split into multiple if statements for readability. First check IP fields, then ports.
98         if (!(inputPacketIpDst.equals(finPacketIpSrc) && finPacketIpDst.equals(inputPacketIpSrc))) {
99             return false;
100         }
101         if (!(inputPacketPortDst == finPacketPortSrc && finPacketPortDst == inputPacketPortSrc)) {
102             return false;
103         }
104
105         // Packets are (most likely) related (part of same conversation/stream).
106         // Now all that is left for us to check is if the sequence numbers match.
107         // Note: recall that the FIN packet advances the seq numbers by 1,
108         // so the ACK number will be one larger than the seq. number in the FIN packet.
109         return inputTcpPacket.getHeader().getAcknowledgmentNumber() == finTcpPacket.getHeader().getSequenceNumber() + 1;
110     }
111
112     /**
113      * Static method to check if two given packets are a FIN and the corresponding ACK packet.
114      * The purpose of this method is a workaround to enforce the class invariant in the two parameter constructor.
115      * Specifically, the following should be avoided:
116      * <pre>
117      *     public FinAckPair(PcapPacket finPacket, PcapPacket correspondingAckPacket) {
118      *         mFinPacket = finPacket;
119      *         // Below line is considered back practice as the object has not been fully initialized at this stage.
120      *         if (!this.isCorrespondingAckPacket(correspondingAckPacket)) {
121      *             // ... throw exception
122      *         }
123      *     }
124      * </pre>
125      * @param finPacket The FIN packet.
126      * @param ackPacket The ACK packet that is to be checked if it corresponds to the given FIN packet.
127      * @return {@code true} if the ACK corresponds to the FIN, {@code false} otherwise.
128      */
129     private static boolean isCorrespondingAckPacket(PcapPacket finPacket, PcapPacket ackPacket) {
130         FinAckPair tmp = new FinAckPair(finPacket);
131         return tmp.isCorrespondingAckPacket(ackPacket);
132     }
133
134 }
135
136 //    /**
137 //     * Sets the corresponding ACK packet in this {@code FinAckPair}.
138 //     * The method internally verifies if the given {@code packet} does indeed correspond to the FIN packet in this pair.
139 //     * @param packet The packet that is an ACK of the FIN in this pair.
140 //     * @return {@code true} if the packet was successfully set, {@code false} otherwise (the packet did not correspond to the FIN packet in this pair).
141 //     */
142 //    public synchronized boolean setCorrespondingAckPacket(PcapPacket packet) {
143 //        if (isCorrespondingAckPacket(packet)) {
144 //            mCorrespondingAckPacket = packet;
145 //            return true;
146 //        }
147 //        return false;
148 //    }