From a3378a3c93e46c34d93472fad2cce45855561833 Mon Sep 17 00:00:00 2001 From: Sebitosh Date: Thu, 4 Apr 2024 14:50:56 +0200 Subject: [PATCH] TcpStreamFSM Tests Signed-off-by: Sebitosh --- .../streams/impl/tcpFSM/TcpStreamFSM.java | 18 +- .../pkts/streams/impl/TcpStreamFSMTest.java | 283 ++++++++++++++++++ .../streams/tcp-fsm/tcp_closed1_rst2.pcap | Bin 0 -> 1036 bytes .../streams/tcp-fsm/tcp_established_rst.pcap | Bin 0 -> 5909 bytes .../tcp-fsm/tcp_established_small.pcap | Bin 0 -> 1410 bytes .../io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap | Bin 0 -> 954 bytes .../io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap | Bin 0 -> 3712 bytes .../pkts/streams/tcp-fsm/tcp_fin_simult.pcap | Bin 0 -> 514 bytes .../tcp-fsm/tcp_graceful_fin1_fin2.pcap | Bin 0 -> 4200 bytes .../streams/tcp-fsm/tcp_handshake_rst.pcap | Bin 0 -> 274 bytes .../io/pkts/streams/tcp-fsm/tcp_init_rst.pcap | Bin 0 -> 94 bytes .../tcp-fsm/tcp_nosyn_nofin_norst.pcap | Bin 0 -> 5761 bytes .../pkts/streams/tcp-fsm/tcp_start_fin.pcap | Bin 0 -> 316 bytes 13 files changed, 292 insertions(+), 9 deletions(-) create mode 100644 pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_rst.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_handshake_rst.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_init_rst.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_nosyn_nofin_norst.pcap create mode 100644 pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_start_fin.pcap diff --git a/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java b/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java index f7e7aa82..a36703b7 100644 --- a/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java +++ b/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java @@ -39,42 +39,42 @@ public enum TcpState{ init.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); init.transitionTo(ESTABLISHED).onEvent(TCPPacket.class); + handshake.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); handshake.transitionToSelf().onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); handshake.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); - handshake.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); handshake.transitionTo(ESTABLISHED).onEvent(TCPPacket.class); - established.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); established.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); established.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); // skipped the end of stream, New stream noticed + established.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); established.transitionToSelf().onEvent(TCPPacket.class); + finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); + finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); finWait1.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::ackOfFin1AndFin2).withAction(TcpStreamFSM::closeFin1SetFin2); // special case FIN + ACKofFin1 packet finWait1.transitionTo(FIN_WAIT_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); // if first fin has been acked finWait1.transitionTo(CLOSING_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); - finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); - finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); finWait1.transitionToSelf().onEvent(TCPPacket.class); - finWait2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); // 2nd fin observed finWait2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); finWait2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + finWait2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); // 2nd fin observed finWait2.transitionToSelf().onEvent(TCPPacket.class); - closing1Closing2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); - closing1Closing2.transitionTo(CLOSING_1_CLOSED_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closing1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closing1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closing1Closing2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); + closing1Closing2.transitionTo(CLOSING_1_CLOSED_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closing1Closing2.transitionToSelf().onEvent(TCPPacket.class); - closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closed1Closing2.transitionToSelf().onEvent(TCPPacket.class); - closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); closing1Closed2.transitionToSelf().onEvent(TCPPacket.class); diff --git a/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java b/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java new file mode 100644 index 00000000..c0797ce8 --- /dev/null +++ b/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java @@ -0,0 +1,283 @@ +package io.pkts.streams.impl; + +import io.hektor.fsm.FSM; +import io.pkts.PacketHandler; +import io.pkts.Pcap; +import io.pkts.packet.Packet; +import io.pkts.packet.TCPPacket; +import io.pkts.protocol.Protocol; +import io.pkts.streams.StreamsTestBase; +import io.pkts.streams.impl.tcpFSM.TcpStreamContext; +import io.pkts.streams.impl.tcpFSM.TcpStreamData; +import io.pkts.streams.impl.tcpFSM.TcpStreamFSM; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; + + +public class TcpStreamFSMTest { + + FSM stream; + TcpStreamContext ctx; + TcpStreamData data; + ArrayList packets; + + @Before + public void setUp() throws Exception { + ctx = new TcpStreamContext(); + data = new TcpStreamData(); + stream = TcpStreamFSM.definition.newInstance("uuid-123",ctx, data); + stream.start(); + + + } + + + @Test + public void testFewEstablishedOnly() { + packets = retrievePackets("tcp-fsm/tcp_established_small.pcap"); + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + for (TCPPacket packet : packets) { + stream.onEvent(packet); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + } + } + + @Test + public void testEstablishedOnly() { + packets = retrievePackets("tcp-fsm/tcp_nosyn_nofin_norst.pcap"); + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + for (TCPPacket packet : packets) { + stream.onEvent(packet); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + } + } + + @Test + public void testFinEndStandard() { + packets = retrievePackets("tcp-fsm/tcp_graceful_fin1_fin2.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until start of gracefull end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testFinEndFinAndAckOfFin1() { + packets = retrievePackets("tcp-fsm/tcp_fin_ack.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until start of gracefull end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); // case FIN + ACK of first FIN + assertEquals(TcpStreamFSM.TcpState.CLOSED_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + + @Test + public void testFinEndSimultanuousClosing() { + packets = retrievePackets("tcp-fsm/tcp_fin_simult.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.CLOSING_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSING_1_CLOSED_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndInit() { + packets = retrievePackets("tcp-fsm/tcp_init_rst.pcap"); + + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndHanshake() { + packets = retrievePackets("tcp-fsm/tcp_handshake_rst.pcap"); + + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(2)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndEstablished() { + packets = retrievePackets("tcp-fsm/tcp_established_rst.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until abrupt end + for (TCPPacket packet : packets) { + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + break; + } + } + } + + @Test + public void testRstEndFinWait1() { + packets = retrievePackets("tcp-fsm/tcp_fin1_rst.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + // abrupt end in FIN_WAIT_1 + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testRstEndFinWait2() { + packets = retrievePackets("tcp-fsm/tcp_closed1_rst2.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_2, stream.getState()); + + // abrupt end in FIN_WAIT_2 + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + + + private static ArrayList retrievePackets(String filename){ + ArrayList packets = new ArrayList<>(); + try { + Pcap pcap = Pcap.openStream(StreamsTestBase.class.getResourceAsStream(filename)); + pcap.loop(new PacketHandler() { + @Override + public boolean nextPacket(Packet packet) throws IOException { + if (packet.hasProtocol(Protocol.TCP)) { + packets.add((TCPPacket) packet.getPacket(Protocol.TCP)); + } + return true; + } + }); + + } catch (Exception e) { + e.printStackTrace(); + } + return packets; + } + +} diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap new file mode 100644 index 0000000000000000000000000000000000000000..7ace673b4df61990589cf2e6bd466e36a8057b8e GIT binary patch literal 1036 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 literal 0 HcmV?d00001 diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap new file mode 100644 index 0000000000000000000000000000000000000000..3f30a64f37677c8fc609bfa381ad39ea9e70076a GIT binary patch literal 1410 zcmca|c+)~A1{MYcU}0bcau^#g2Q2JhXE+MvfG~q_(XThE3^K~sQx4S zU2F|4QjJm*Eey;oOwtSuQ;d=<6B7*#k_}AK%q`O_3`})goNY6UL1bcz9Ty`L7b61` z7%?(3EC87muoy&2vWO&S7ANPsCKo4_`X^q-SAbpl4`kY-(zzXQ*eQXJ~9} z33M(B9&orZ8|7Y85lqj3W`|9R?Zesgz`pSI~ySJEl}YcNE<_WLMfIgRnYjcB<>`C ztEhoclStF)h5MZitqzG^eg<}eGdN09K>@}Nj2IRm1P6sSSXLF69}pe@Y5;rS;}Ld- z?Z6ZX!WgM?Wqu6E1DU%}Q{}a@|CnXW10??cUm6T@KVvO0RaRp0!r^=OluHhJx6f8o z@cOvxn}8GFn;pLU_Hu$8$>yh{kCYcm)(0-CDfZpBGO?Q71H6pC!7p6z_bLy h=;@F_BR>}8fov<(ba=xLm<~4u2>fqp*#Oc80sxqUag+c6 literal 0 HcmV?d00001 diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap new file mode 100644 index 0000000000000000000000000000000000000000..ebb4aeee1ae29bb7fe60abcd8ac866dae6ebbfb5 GIT binary patch literal 954 zcmaLVL1+^}6adiK&9;U}pfPeNL1LKprrJM6sqGyi|AY(JHe zf({-9N${~(_%P9O-;1uoSOoI1KRG>xCC>=1&Muc~M%M z-Z}MF{JbT1zJCKFS!pRLvfG0~z1w15a=DHPx%0t+Tr+EaP0pW%lD>Y7wW(M?y#ti%aU|EDN8P8Fg_lgq887cNhO9S{5Phe z8ev(>Ab8j?>HZ`~{$*SO9`Ek^DJ10ut{$6nY3vV&;2&0qPx zrnk8}z0>V0bV(;#o(1kaw|x=hn}hM1l8cB2u_*$@(5!QoY0bq z^J-iTtAWwD9~<`45}`bTCl?5I;4Fk;MpiPAse@-NYC4$Pm}eNpHpLd@WS+tCv9Umm zI~JYC)*RNf)UC;shIK|TwK>+)jOb<#>jt;7M6Bb08#bi`W^C%mrp|S&`f)a3&IYP| zySjd44>;8x7&u(9Zr|99uLM01y8L9nVx@WyeA+lt-*yCy7+C=i$ZX} ZA{uJemp|96X1)^CtYe}Bp`Jm6{sD_986^M! literal 0 HcmV?d00001 diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap new file mode 100644 index 0000000000000000000000000000000000000000..d46ac0b5f99c2023d896d89fca35c52457dfad57 GIT binary patch literal 3712 zcmdT{eN0nV6hH4#T6{5p8r;)~dpebmO4k+zRB#kp6j#a%#ekU$uu+9+pth)SX|$kQ z+%QX~OEj4*poz(hC{745%M@KgT;j(c!-ZtYf{eMTh?;FWoceaoElB&?5&u}0?7p0P z&%5v3^YJ_P+}`$m^JN6{Tk?Eu%)By@|SZ zB2N^i%dH<|x;91BB`S)16QB{mD%orW)>3qrg^BnD4OJY*wqqnwM`Ge?YRWq3^ar zn_N&ZCK)7Su`qA;p*PFX5h8|nFiwkoOCsWQuDk0_qAWhXFRcS;!i5x9T8aSYktYg^ z>$LtS7FSAWTz3)?ql>br{Ppk&Era(JXHlMqIMI2DhKZ&;WBl8|%lVSSR$5kE@`far z6t60=6iFt75qP`BQDkv2wyd_Ux_-M?+EDM2%IZCvHhSu}y1i1m!BmBEMJ9u}+-Nk9 zFEJZTz&lDMdy!-`n9MAk8>s^5aBmWY0W<1b8WEU_2+T1AX5+j?%-Ji?!#NZ=oN3Vz zXT}oHSV*2s0X+GQ^MBrVrRUL)SGXDu_JZx~ncm;~9_F4}ad`jX*$KMJZCb5f%SA`? zt(%SSwq_fbYXyAQa7~#!KUX^?^@G(-kEU}x&)5$FtbnrtHY!M3IGSoqZe8^9g1Y*K zifY$}+B#QtMQvS0NkRS+Q)X7Kd$X&`>#C^TSXb|HQI<;=3t&Y*iPTx*P zwov#Q&uAJu#sTC}uLhqi921&89Gr80;pJ${&Lpx&tpOi85w+auL>%G@5kWfLY2;ug zSaEbh8k9mg_EyM83{Ci5K-|5EKow#i1@r$w&JZLO9kNCTF)&^r%}Cj(<9J(P35K>!b?gD9+ys`WqTQXMsf*3ochOMjC^ z7N7c7xp5V6S(DWfKj5c2$_lEZ9kdFlj-Eo>cH`C~Ud@*AWYaF<@TnvbY*m2)RtJqD zi0nNc;@S6vkM<6ZR}&>Y4Zjw{+OecN8L03X_i@6GOTCBZ<@mf82lnN>NVfiqs(Z;K zz+6P1D6Hz@eVLR2^$nrP(%p6UzAXNkI4_6cGW0wH5v$HmRX2o^Q{XJL>5S1-bN|cz zdK1ywkAV=W_bcqt-z2n7Iz#vC-TN*5yHjNG$*2CSv<%7#t*ykdbDx;;raa>ZdI+s@ zEN%{W60FB3@(HpYPZ$|blLff=b!x1l(#=>=I76h)Csg~gBIR>;N_W?YT^1i*kUpVh zz$%J)$nweNSf_OdJk()Dp?`|<3L_m9%K0QfHGLjE=s=z*45K6ZUl>M9LNOY#BStR7 zsJwGFErV@@(L&-xT|W)uP3ta=;s=0>CAk==iKh>HXHY*Ip$fE2!cAuQ*pRb>1sqVs zdJ?FCGLPL=V=aa}Q$}8%^*s!w25+c}LjLz=OnS@xmHDr_fsgdI^fw=*TW&|*m=6pK z1$EI8!j2L3ZQiNB#dI~Qun6eFTW%ZMW{^Z!G?3be`z4p-6=sv1Ka?3{P<`Qd`c_0r zfAc|1{{u|_9LdiVO&C$BU97*&BHW{j@ByMl_uanA zz-{(bif{|rRz(=tZ+<$P$XIHyF?DpszU+w6;cfJuD2osOkbGnTCXA_~QyDvl#^`sQ zC@ivM{T&wB@zBWbi5S@}d79FG3jc`(8aY0Sx-#&UD6A@zeK`>wzlDy^lBc2LYXjlC bmQCu>F!SgOl}EGCquaPM+fR)I`~mnI#Y38P literal 0 HcmV?d00001 diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap new file mode 100644 index 0000000000000000000000000000000000000000..ac9047be7295393f1f954b52cace9db95ae4d5ab GIT binary patch literal 514 zcmca|c+)~A1{MYw`2U}Qff2|#w}3CL<_;r+8ITRaV88?>895kS85lG`${g5g&eSt7 zFaa?mg9t+a0|WC5keUD{1%}?q5L0&CMKc9tJWvmYDU2YKfM$p=FtA?$n)EV2NP(es z6U3CJduXN*7Y( literal 0 HcmV?d00001 diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap new file mode 100644 index 0000000000000000000000000000000000000000..b3752ea074b24489817ff2ffa184e1f10da8f8c5 GIT binary patch literal 4200 zcmc(iXH-+!7RPToM0yuSLXiPQAfY3IAgFYMfRq901TZvdB0W+1AV!KxCxX%x5Co+> zK#&fC3`M#$QJNIR&|ZRT);PZR{4yWjS!>^Qa&qo3d!Kdo&i?nmEXxK1P~fit1whmf zl(0A0W*IP`N}baTV82RP6af%S>ev10fU^LAHcL|jLg4)=MJ((tMJx`;0U{t5pv@UU z+w2M6-#5Sk02m6xfh{Lk%@5^1Fy+#`}&$_$8Km-gp zupe2lYi1cVR*(MwVpYxoz#)zpvOo!D%i`)OFJBOa#VD1 zu$Q-$khOKRmB3&er6tggiWmtr8l$LaC#T?Krz8&mU;p9T`!olT5p+0jZwVi77aV|n z6ZRF`-xmNtsOxwe0@4`{7l(sjFmM(DX^AEv&9aW7h2ZqZBeV!#*g#vvl~fI9)nNyV^cLBJqbHub@jgMh#Q0jeKl0+iR50Odjxplsj1 zM)1M3jU?4IbA-`?v=wkRFPeuBqG@2n$Lc_H`I>}-JsKnF;w@?GKqf#&&;-aJZ6i00 z49g)v+MonTGh4My+NjyN3@sUFH4aWfXMu9pKSBAGMpwDL4K(0!^|R^7EZF z$&Q}l=E6X?ueeOL)WMz7_&O3zgCxtU*tGpE-Sb&hSluUOQ&$#ID|yYluQN=*I+H|) z=bNZ-W3G@~a)tiLp}EKgi$yQJiEUKH;CYwns-#;*ZQb6D^q

Y>6>1%!Z;zJwIP6 ztS|pntMGk(+X8!N0C8d9U6WJnr=pQxheFVdFE#J_f(DyriOm$G#k!LwL+{PaBx~jr z+goV7S>Ds|+F&y;_`o~d+CBNmk;E;1-&01U{grws&XV^v`KS-J#pH#zu{u+Z$L)j> zSCn}_nxXHRXz`%`d-0p5BD2cSLkbh>7QD_0jWQ4A#53>ybLWOoW%_ z`lbYVp(D%s&I!6~&51`7AA+3VT?N;|0+>d8iw)KKm||W^zbUD%XNJmvB`vz_a}mM< z=LYV#u~AMx`lF%3I63T!?#V%$F`Evdq*~DD#-yu34vmWWhXQ%jzUw3aNG3#$1<;apvQoES+3b+iTKVzdS^Ot^6%ay0&|Eayf*zl)`NP zi1A5wjhXvaK0AExPCTEhN_bv5%X(IT_@o%KN-8rdHg<3EB{8VyVvq6>Db@yiWTWu( z0xUT@!}6ZDb)}c26G#}2!~`Deh`3%Mr#f~B~A?biBGbxv>JVRYk#@U45bhxzmkC| z`>>P=o>89XG!~{GWbWl`{{jvfkgTGVkD@75;8N0QjVI)+^2A&7W^t`s$1K zs$-9-n&0uX%q@sjwbQJMms0UhLwLh^J>rkyYRp)pn*YuJVcqb@w zn%RFXCHVQYek6*dQbtPG7-{NQ#im_JJNE&CQ(D(}Z?@A^dTZg#wcbqYlHP}0p7$sl z)xxjbg?3BTUc{PQ$?dMyQ%hjob%wc!lW^@p3}@hdpJ&SvAKzT&JQH-XepCB8DUP+1 z-n5Nrr}@bMIau(D=tKxCH#XU=)#2LAg<$tf>MT6Ff#rfIu}A$zjdUR0ZBbXdDuKCT zzOPPFGZHh}qthP8BK=CXow18NOf_0Tj-zE~{P@`BGi$l$7zD<*1~zK;C+fm+>d#dP zt)vn&`sS+|)f|uSKb!7f`c19bkUgx9ND3~__u!X0S>Cgz9cahVgZ`L6u*y}zxAv~W z^|^F)hYEj+%n9LzHsp-*qx&p)&u zxrfnPsxOJhm%YdnOInj9%Yxa--0rLv1II?fioz>y`aM~DRfz6;D{_QV&5dmJ#gb#( zjvW@VF&zHXxTR)UGUI5Vgrmp3x_ZxHXer64j7w*pQ!e8A@6OUeLp@g>+HU+Iqlt>R z2{&aNc?a?g-6SsNB{#3P@o&$3+&cDLRbz6|NjHfl-jUtNWLH9aq!y|ALB=30PYbls zTODbEQg;YR(h-(vXkaY|r?x2FXmM~$gvbX*yi=|piwd3a4s%a0XdER==cubu>@M0| z{7Oi^Gm_sTw4AEl8ge*F35E@LpL_PzvL{jfT&QxQVBQra(_Qz~=2Pj5WfVy+4-khf zUBN8g2^=#TduskX|5Rs^O26turv%$|%g;3`jZ8|IfeLil3%$+?egX5S$Ir;FSq@B9 zg}n8mk5u2E8ONSq&f~*48E0rUgfDhwzlNFkx(3+Q{E}HzsNau(@==UXp{bFhn$OP9 zbm31&Oa?G4%RGdaR>K@X8Mha@7Oqk@PNxlZG$wiGj~bsy5y46lJh@m-Ue-Z#$c(F- z_!!CMK{3MZM@HvP>Kwh0d2Zdn+WW?N9F{jsbycWzdf%*V!sYDaKm!*7*Q^Y?l5RwG zt9zH$)5;kQDIfEy(y1Gb-S^}W#xlA%OX8&p9t)o{6_*MOlWNpmVh*D`SrhT%lS^Oo zIgV9C`E(Bq7MgZSVmvV$!$tv3br-=y&^vVE-f+WhS?4*cT6e!rKgJ}y#dgo8u}D+{ zk56P}byw*xdishVQMamRjRl-}bLK8cnvZ?p8*CRS6}g>~`DMecm6f*?ba6-=_fX@j z^2q}Cx0@WC{xw3oQ#zII7ZfvclZxoS;zsYa1;cD!B(fbdRnwme4#z&VwbBXxjTquS zZkx}Oa>y|*E=I&4KdffH3h1cRa(gfw(NvG=R4ripjciV72SssAP&~X0S~PP%ein;1 zDPQWlr82gS89G-Z=Tk9Gaco)EE3fE+BV5er>yWQ&^YrJPTMfI}Q>Ye7a_*bR{(qLubCj5bS3{&bS7%kk3jW;Kjo})~@n#UmDjOJaEn{XS zq-DIJxZrtX3NIuu!l#AIkUVumQMD>Ha$;HH@Hu*%fY_G7?AnsN?0I13ExtgHz}(m* zOsmRhYE`E8sRsu9*u{$lx%}5I9v7f3AWH4Kfu6_TI__c|1Sq0XX!c{r4P=;Q9dz#^ zf3NKVq6f8|XMg`>I{>^?y{p3xtc2<0}glsXYG_XZqYnzSFnGdb&9 zA>WbUeVwtA#E^;iDw$P#R*37uXGj+4QgXieZsrC7;9%guemnutR*>ueJb`p9&t6P# z2vs0-cQJ<1)B;T}{NL3AQ2RHvK%uII>om0hkh85F`l5KJ=YL$O(^vhTpUa zCwwOaz<`H0+Ur}FPi@tia~#q3-~QPxp%!_8tvwUn5o2qOA<@M$9>KqDx47+9EL}0m zikqS!T&t}}i6{oJA4$?I_FAAn_-FeD?9H)Ls_xUV2#L!Jt$J4lR zwv0$Ztqv(ReP$NN0>D2ITNL0xE&-?iIskwsm?&{sfQXIochP}BtiV2X3@=!|97E8H z#n}8JFhRDPF)Nc3}^Y#l*t8g@uWOi@{8F6G)tqnVB7AvRFI9 zWQds%^C2eJs5>w;urV-zOa+t9c3-88eMfueId2@=u0}S9qrg^ac8S_#@BSzQYltkTFsvGr&W?%Q)TOhoJ;QG z{Q1A{`yc1z%(4Aj1c*itUNoZc3FSPkeK|^q8u4c`(1?nj8{<*rxU;Nl07^$_HJ6@( zYJy6yHw|Nl;1N8qmRjyohaMI)+f`c8kM4^H$TVcs8-fN-T9L7$JA@Jl1k`Zk#xI2< zX=p?CDDk@7jlwH&m%2jj%&SLs(jQ2d;VBY9KXi#Tl8>=83Z}?gK;9~fLUn_uB+u?CU5od2i3y})XAj8kuiQFvWY}s6IXTxH|%pv0~& zO@MqQU686t*5sw>#^_8Ahs9cKpNuc0>2&dV`SEF~QW-hbnJS()RluDLuF~${tZ>!h zFu}D{&T6(4S5_Dt7JMfyHQ8b(M+5t-qz<8b^9&4M#H`3LBV>32!NUy+2Wnikju+pt}-r`|p>7G-&sC;wH-iYNbAGQg?&*?)Zy%)Rt zF0k@XV1*^|@WvBm1-?xM$3!puGVasQ2L89w9tx$O4H4Nxgp@M ziOdmVqG|jG1P+T8qKTM1GJL~|ir_K`fu)5$VfDS3+0&&Jx#@#-cosq(dgpZ>0!ufn zEg2u#*6BL1cktYXx*GN;3+kFOcMUkyN*ppnSk*jH0)GxGLENy1ChKNG%S3Q1h7OZ4 zQ<$j=DI=4~6A}^`8KYpPN@Wz~ru$3#2`C}(b}TMnYG)GO$~4}*jk+vm4i}KrzI*|A z!v~Vuo$`P+EekZb>Tm14o4BTxh8zT2{536YRo%G}zl-bW`CG=;$d*%w>Te9+)DklN zHeiLCR{WSXtv(?j@Yw)7+uK>uY%z1Zi2!ep>kWKUQb6F@0Q@qqEnL$|Bmsbj3>|x< z|9JgZS9^}%sj_~4e){^OoWuV6oBzJ^#}*I+y^})8bz} zV`mG|E&NSn`003+%K;r<^42k2BWBJFz}0iXAH&ErbV%PzKTcY^^3onc6IBPw6jl-h}+)zZ&mDs(;wGq-kc9ItBK5IAhXIV znER5F8f(CmG!a64Q}V9arH-iAif)eyoRU$?)=Tzkbu(1Sb_xAmea@b+A;iy1O}rJ@#bc?6}U$49DqF>^17q~wBpA{CXcucc=2h}9x5 zUAKP4gwT{}&CMIs7bH2+XI^-I()!T^CN-3ZwGhN=@)1jb^@803UUVT?ZOnHOI>PCZ zMvL9Zr5o)aiqF8GT$uQKdKAy< zM<$mmvEQ_(z)s{{A<1R<5jQ;u%=8yf?9c1V#HB*C4?9bSUmtK)oB4w7UXV*Y`ixf8 zb92mRWExtmujCQqn|&_BhZ$cS$q6pmwECl)CsM9(|JZCl@Eo|13SxML2jF}P?v8&L zh6eyJTv+Ih*%+`@KT80VZugF-(PwZxIklpDmkT}nu7|Yh&x6;@Jo?SFRWFj8l||AbMW0%AP=gSC zi&-YaPofLeCA<Tk9=vq)p7Yr(${IZ-)T*hjj4dk^7@Y#1w>1$(1qGWt_E3Dpp>NX$|?Z60|8Kfms3CZfLtME^{sCR^@ z4)Y7Wk zXP&k1zO-$2gn^)^MiFHlpzMK1QueVsxAK4L+|j?%xu0mS{K7Zc^~{c4mabOk$owU5M8rGjH&>dCp6xjFYKK>t^sQIdEgBT)_trlV0A)(= Wf)O1mmP8ED4!d`iX0QRWL6||f=+_%n1{vk+DT_H6Tp1WN@~a#e z8rZUB*9fHW0x=gC_W?GO>AZ7%V}EuB2>xE`a2%u!1VCoZ(_>~Z1F}IFVhY4Wh*`C} zKxQ#8fXo4zcYuwH8)Od9ytx4a-)AcXLrgi0-;~{WOc6wwQm&8JUo~KVNu&8|F3>$a N0RljO9Rqm*1OQ{~QPKba literal 0 HcmV?d00001