From 24ed3bd0d9dccd357b54c020e3ad6803286e71cb Mon Sep 17 00:00:00 2001 From: Jason Ernst Date: Thu, 8 Aug 2024 11:54:08 -0700 Subject: [PATCH] Added tcp pcap dumper + tests --- .../packetdumper/AbstractPacketDumper.kt | 3 - .../PcapNgTcpServerPacketDumper.kt | 177 ++++++++++++- .../packetdumper/PcapNgTestHelper.kt | 38 +++ .../filedumper/TestPcapNgFilePacketDumper.kt | 50 +--- .../TestPcapNgTcpServerPacketDumper.kt | 233 ++++++++++++++++++ 5 files changed, 456 insertions(+), 45 deletions(-) create mode 100644 packetdumper/src/test/kotlin/com/jasonernst/packetdumper/PcapNgTestHelper.kt create mode 100644 packetdumper/src/test/kotlin/com/jasonernst/packetdumper/serverdumper/TestPcapNgTcpServerPacketDumper.kt diff --git a/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPacketDumper.kt b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPacketDumper.kt index 9cf9935..0659e89 100644 --- a/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPacketDumper.kt +++ b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPacketDumper.kt @@ -2,11 +2,8 @@ package com.jasonernst.packetdumper import com.jasonernst.packetdumper.ethernet.EtherType import java.nio.ByteBuffer -import java.util.concurrent.atomic.AtomicBoolean abstract class AbstractPacketDumper { - protected var running = AtomicBoolean(false) - /** * Dumps a ByteBuffer starting at position offset, and going until position length. If length * + offset is greater than buffer.remaining(), it will dump until the end of the buffer. diff --git a/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/serverdumper/PcapNgTcpServerPacketDumper.kt b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/serverdumper/PcapNgTcpServerPacketDumper.kt index 2bab323..129be10 100644 --- a/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/serverdumper/PcapNgTcpServerPacketDumper.kt +++ b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/serverdumper/PcapNgTcpServerPacketDumper.kt @@ -1,21 +1,125 @@ package com.jasonernst.packetdumper.serverdumper import com.jasonernst.packetdumper.ethernet.EtherType +import com.jasonernst.packetdumper.ethernet.EthernetHeader.Companion.prependDummyHeader +import com.jasonernst.packetdumper.pcapng.PcapNgEnhancedPacketBlock +import com.jasonernst.packetdumper.pcapng.PcapNgInterfaceDescriptionBlock +import com.jasonernst.packetdumper.pcapng.PcapNgSectionHeaderBlockLive +import com.jasonernst.packetdumper.pcapng.PcapNgSimplePacketBlock +import org.slf4j.LoggerFactory +import java.io.BufferedOutputStream +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketException import java.nio.ByteBuffer +import java.util.Collections +import java.util.concurrent.atomic.AtomicBoolean +/** + * Sets up a server listening on the specified TCP port. (default is DEFAULT_PORT) + * + * Wireshark can then connect to it as follows: wireshark -k -i TCP@: + * + * Dumps packets over the network in the PCAP-NG format. + * + * @param listenPort The port to listen on. + * @param isSimple If true, the file will be written in the simple format. + * If false, it will be written in the enhanced format. + */ class PcapNgTcpServerPacketDumper( - val listenPort: Int = DEFAULT_PORT, + private val listenPort: Int = DEFAULT_PORT, + private val isSimple: Boolean = true, ) : AbstractServerPacketDumper() { + private val logger = LoggerFactory.getLogger(javaClass) + private val isRunning = AtomicBoolean(false) + + // TODO: use a more kotlin way to do this with coroutines instead of a thread + private var socket: ServerSocket? = null + private var listenerThread: Thread? = null + private val connectionQueue = Collections.synchronizedList(mutableListOf()) + companion object { const val DEFAULT_PORT = 19000 } override fun start() { - TODO("Not yet implemented") + if (isRunning.get()) { + logger.error("Trying to start a server that is already running") + return + } + listenerThread = + Thread({ + logger.trace("WiresharkTcpDump starting") + try { + socket = ServerSocket(listenPort) + } catch (e: Exception) { + logger.error("Error starting WiresharkTcpDumper: $e") + return@Thread + } + logger.trace("WiresharkTcpDump listening on {}", socket!!.localSocketAddress) + logAllIPAddresses() + isRunning.set(true) + + while (isRunning.get()) { + try { + val client = socket!!.accept() + logger.trace( + "WiresharkTcpDump accepted connection from {}", + client.remoteSocketAddress, + ) + + try { + val outputstream = BufferedOutputStream(client.getOutputStream()) + outputstream.write(PcapNgSectionHeaderBlockLive.toBytes()) + outputstream.flush() + + val interfaceblock = PcapNgInterfaceDescriptionBlock().toBytes() + + outputstream.write(interfaceblock) + outputstream.flush() + + connectionQueue.add(client) + } catch (e: Exception) { + logger.warn( + "Error writing to wireshark client, it may have " + + "disconnected from us before we wrote the pcap header: $e", + ) + continue + } + } catch (e: Exception) { + logger.warn( + "WiresharkTcpDump error accepting connection, possibly" + + " shutting down: $e", + ) + continue + } + } + }, "PcapNgTcpServerPacketDumper listener") + listenerThread!!.start() + + // wait for isRunning to be set to true + while (!isRunning.get()) { + Thread.sleep(100) + } } override fun stop() { - TODO("Not yet implemented") + if (!isRunning.get()) { + logger.error("Trying to stop a server that is already stopped") + return + } + isRunning.set(false) + socket?.close() + logger.debug("Waiting for listener thread to finish") + listenerThread?.join() + logger.debug("Closing all connections") + for (connection in connectionQueue) { + try { + connection.close() + } catch (e: Exception) { + logger.error("Error closing connection", e) + } + } } override fun dumpBuffer( @@ -25,6 +129,71 @@ class PcapNgTcpServerPacketDumper( addresses: Boolean, etherType: EtherType?, ) { - TODO("Not yet implemented") + if (connectionQueue.isEmpty()) { + // if there are no connected clients, no reason to dump + return + } + val conversionBuffer = + if (etherType != null) { + prependDummyHeader(buffer, offset, length, etherType) + } else { + buffer + } + val packetBlock = + if (isSimple) { + PcapNgSimplePacketBlock(conversionBuffer.array()) + } else { + // todo: timestamp + PcapNgEnhancedPacketBlock(conversionBuffer.array()) + } + + with(connectionQueue.iterator()) { + forEach { + try { + val outputstream = BufferedOutputStream(it.getOutputStream()) + outputstream.write(packetBlock.toBytes()) + outputstream.flush() + } catch (e: Exception) { + remove() + } + } + } + } + + private fun logAllIPAddresses( + excludeInterfaces: List = listOf("bump", "docker", "virbr", "veth", "tailscale", "dummy", "tun"), + ) { + try { + val interfaces = java.net.NetworkInterface.getNetworkInterfaces() + if (interfaces == null) { + logger.error("No network interfaces found") + return + } + for (networkInterface in interfaces) { + var excluded = false + for (excludeInterface in excludeInterfaces) { + if (networkInterface.displayName.contains(excludeInterface)) { + excluded = true + break + } + } + if (excluded) { + continue + } + if (networkInterface.isUp.not()) { + continue + } + val addresses = networkInterface.inetAddresses.toList() + if (addresses.isEmpty()) { + continue + } + logger.trace("Network Interface: ${networkInterface.name}") + for (inetAddress in addresses) { + logger.trace(" IP Address: ${inetAddress.hostAddress}") + } + } + } catch (e: SocketException) { + logger.error("Error getting network interfaces", e) + } } } diff --git a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/PcapNgTestHelper.kt b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/PcapNgTestHelper.kt new file mode 100644 index 0000000..b2f950e --- /dev/null +++ b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/PcapNgTestHelper.kt @@ -0,0 +1,38 @@ +package com.jasonernst.packetdumper + +import com.jasonernst.packetdumper.filedumper.AbstractFilePacketDumper +import com.jasonernst.packetdumper.pcapng.PcapNgBlock +import com.jasonernst.packetdumper.pcapng.PcapNgInterfaceDescriptionBlock +import com.jasonernst.packetdumper.pcapng.PcapNgSectionHeaderBlockLive +import com.jasonernst.packetdumper.stringdumper.StringPacketDumper +import org.slf4j.LoggerFactory +import java.io.BufferedInputStream +import java.io.FileInputStream +import java.nio.ByteBuffer + +object PcapNgTestHelper { + private val logger = LoggerFactory.getLogger(javaClass) + + /** + * Verify that the file has the correct headers, advances the readBuffer beyond these headers + * and returns a list of the blocks. + */ + fun verifyHeaders(readBuffer: ByteBuffer): List { + val pcapBlocks = mutableListOf() + + // we expect the file to start with a section header block + pcapBlocks.add(PcapNgSectionHeaderBlockLive.fromStream(readBuffer)) + + // we expect the file to have an interface description block + pcapBlocks.add(PcapNgInterfaceDescriptionBlock.fromStream(readBuffer)) + + return pcapBlocks + } + + fun readFile(dumper: AbstractFilePacketDumper): ByteBuffer { + val readBuffer = ByteBuffer.wrap(BufferedInputStream(FileInputStream(dumper.filename)).readAllBytes()) + val stringPacketDumper = StringPacketDumper(logger) + stringPacketDumper.dumpBuffer(readBuffer, 0, readBuffer.limit(), false, null) + return readBuffer + } +} diff --git a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt index d0d87a4..5afa347 100644 --- a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt +++ b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt @@ -1,19 +1,16 @@ package com.jasonernst.packetdumper.filedumper +import com.jasonernst.packetdumper.PcapNgTestHelper.readFile +import com.jasonernst.packetdumper.PcapNgTestHelper.verifyHeaders import com.jasonernst.packetdumper.ethernet.EtherType import com.jasonernst.packetdumper.ethernet.EthernetHeader -import com.jasonernst.packetdumper.pcapng.PcapNgBlock import com.jasonernst.packetdumper.pcapng.PcapNgEnhancedPacketBlock -import com.jasonernst.packetdumper.pcapng.PcapNgInterfaceDescriptionBlock -import com.jasonernst.packetdumper.pcapng.PcapNgSectionHeaderBlockLive import com.jasonernst.packetdumper.pcapng.PcapNgSimplePacketBlock -import com.jasonernst.packetdumper.stringdumper.StringPacketDumper import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory -import java.io.BufferedInputStream -import java.io.FileInputStream +import java.io.File import java.nio.ByteBuffer class TestPcapNgFilePacketDumper { @@ -27,36 +24,13 @@ class TestPcapNgFilePacketDumper { // delete the file created for the test to cleanup try { - // File(dumper.filename).delete() - // logger.debug("Deleted file ${dumper.filename}") + File(dumper.filename).delete() + logger.debug("Deleted file ${dumper.filename}") } catch (e: Exception) { // ignore } } - /** - * Verify that the file has the correct headers, advances the readBuffer beyond these headers - * and returns a list of the blocks. - */ - private fun verifyHeaders(readBuffer: ByteBuffer): List { - val pcapBlocks = mutableListOf() - - // we expect the file to start with a section header block - pcapBlocks.add(PcapNgSectionHeaderBlockLive.fromStream(readBuffer)) - - // we expect the file to have an interface description block - pcapBlocks.add(PcapNgInterfaceDescriptionBlock.fromStream(readBuffer)) - - return pcapBlocks - } - - private fun readFile(): ByteBuffer { - val readBuffer = ByteBuffer.wrap(BufferedInputStream(FileInputStream(dumper.filename)).readAllBytes()) - val stringPacketDumper = StringPacketDumper(logger) - stringPacketDumper.dumpBuffer(readBuffer, 0, readBuffer.limit(), false, null) - return readBuffer - } - /** * Test that the file is created and the correct blocks are written at the start. */ @@ -64,7 +38,7 @@ class TestPcapNgFilePacketDumper { dumper = PcapNgFilePacketDumper("/tmp", "test") dumper.open() dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) } @@ -75,7 +49,7 @@ class TestPcapNgFilePacketDumper { val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) assertEquals(buffer, ByteBuffer.wrap(simplePacketBlock.packetData)) @@ -88,7 +62,7 @@ class TestPcapNgFilePacketDumper { val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) dumper.dumpBuffer(buffer, 0, buffer.limit(), false, EtherType.IPv4) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) @@ -113,7 +87,7 @@ class TestPcapNgFilePacketDumper { dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) dumper.dumpBuffer(buffer2, 0, buffer2.limit(), false, null) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) val simplePacketBlock2 = PcapNgSimplePacketBlock.fromStream(readBuffer) @@ -129,7 +103,7 @@ class TestPcapNgFilePacketDumper { val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) assertEquals(buffer, ByteBuffer.wrap(enhancedPacketBlock.packetData)) @@ -142,7 +116,7 @@ class TestPcapNgFilePacketDumper { val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) dumper.dumpBuffer(buffer, 0, buffer.limit(), false, EtherType.IPv4) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) @@ -167,7 +141,7 @@ class TestPcapNgFilePacketDumper { dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) dumper.dumpBuffer(buffer2, 0, buffer2.limit(), false, null) dumper.close() - val readBuffer = readFile() + val readBuffer = readFile(dumper) verifyHeaders(readBuffer) val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) val enhancedPacketBlock2 = PcapNgEnhancedPacketBlock.fromStream(readBuffer) diff --git a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/serverdumper/TestPcapNgTcpServerPacketDumper.kt b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/serverdumper/TestPcapNgTcpServerPacketDumper.kt new file mode 100644 index 0000000..7862075 --- /dev/null +++ b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/serverdumper/TestPcapNgTcpServerPacketDumper.kt @@ -0,0 +1,233 @@ +package com.jasonernst.packetdumper.serverdumper + +import com.jasonernst.packetdumper.PcapNgTestHelper.verifyHeaders +import com.jasonernst.packetdumper.ethernet.EtherType +import com.jasonernst.packetdumper.ethernet.EthernetHeader +import com.jasonernst.packetdumper.pcapng.PcapNgEnhancedPacketBlock +import com.jasonernst.packetdumper.pcapng.PcapNgSimplePacketBlock +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.slf4j.LoggerFactory +import java.net.InetSocketAddress +import java.net.Socket +import java.nio.ByteBuffer + +class TestPcapNgTcpServerPacketDumper { + private val logger = LoggerFactory.getLogger(javaClass) + private lateinit var dumper: PcapNgTcpServerPacketDumper + private var tcpClientSocket: Socket? = null + + private fun connectToServer(server: InetSocketAddress) { + tcpClientSocket = Socket() + tcpClientSocket?.connect(server, 1000) + } + + private fun disconnect() { + try { + tcpClientSocket?.close() + } catch (e: Exception) { + // ignore + } + } + + @AfterEach + fun tearDown() { + // just in case a test fails, make sure we clean up + dumper.stop() + disconnect() + } + + @Test + fun startStop() { + dumper = PcapNgTcpServerPacketDumper() + dumper.start() + dumper.stop() + } + + private fun waitForData(): ByteBuffer { + logger.debug("Waiting to receive data from the tcp dumper") + var available = 0 + do { + available = tcpClientSocket?.getInputStream()?.available() ?: 0 + Thread.sleep(100) + } while (available == 0) + logger.debug("Available bytes: $available") + + val recvBuffer = ByteArray(available) + var bytesRead = 0 + do { + bytesRead += tcpClientSocket?.getInputStream()?.read(recvBuffer) ?: 0 + Thread.sleep(100) + } while (bytesRead < available) + logger.debug("Received $bytesRead bytes from the tcp dumper") + return ByteBuffer.wrap(recvBuffer) + } + + /** + * Test that the server can be started and stopped with a client connected. + * + * Also tests that the client receives the two required header blocks upon connection. + */ + @Test + fun startStopWithClient() { + dumper = PcapNgTcpServerPacketDumper() + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + val readBuffer = waitForData() + verifyHeaders(readBuffer) + + dumper.stop() + } + + @Test + fun testDumpSimplePacketBlock() { + dumper = PcapNgTcpServerPacketDumper(isSimple = true) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) + + readBuffer = waitForData() + + val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) + assertEquals(buffer, ByteBuffer.wrap(simplePacketBlock.packetData)) + } + + @Test + fun testDumpSinglePacketBlockWithDummyEth() { + dumper = PcapNgTcpServerPacketDumper(isSimple = true) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, EtherType.IPv4) + + readBuffer = waitForData() + + val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) + + // parse out the ethernet dummy header + val packetData = ByteBuffer.wrap(simplePacketBlock.packetData) + EthernetHeader.fromStream(packetData) + + // remaining data in the stream should now match the original buffer + val recvData = ByteBuffer.allocate(packetData.remaining()) + recvData.put(packetData) + recvData.rewind() + + assertEquals(buffer, recvData) + } + + @Test + fun testMultipleSimplePacketBlocks() { + dumper = PcapNgTcpServerPacketDumper(isSimple = true) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + val buffer2 = ByteBuffer.wrap(byteArrayOf(0x05, 0x06, 0x07, 0x08)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) + dumper.dumpBuffer(buffer2, 0, buffer2.limit(), false, null) + + readBuffer = waitForData() + + val simplePacketBlock = PcapNgSimplePacketBlock.fromStream(readBuffer) + assertEquals(buffer, ByteBuffer.wrap(simplePacketBlock.packetData)) + + if (readBuffer.hasRemaining()) { + val simplePacketBlock2 = PcapNgSimplePacketBlock.fromStream(readBuffer) + assertEquals(buffer2, ByteBuffer.wrap(simplePacketBlock2.packetData)) + } else { + readBuffer = waitForData() + val simplePacketBlock2 = PcapNgSimplePacketBlock.fromStream(readBuffer) + assertEquals(buffer2, ByteBuffer.wrap(simplePacketBlock2.packetData)) + } + } + + @Test + fun testDumpEnhancedPacketBlock() { + dumper = PcapNgTcpServerPacketDumper(isSimple = false) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) + + readBuffer = waitForData() + + val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) + assertEquals(buffer, ByteBuffer.wrap(enhancedPacketBlock.packetData)) + } + + @Test + fun testDumpSingleEnhancedPacketBlockWithDummyEth() { + dumper = PcapNgTcpServerPacketDumper(isSimple = false) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, EtherType.IPv4) + + readBuffer = waitForData() + + val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) + + // parse out the ethernet dummy header + val packetData = ByteBuffer.wrap(enhancedPacketBlock.packetData) + EthernetHeader.fromStream(packetData) + + // remaining data in the stream should now match the original buffer + val recvData = ByteBuffer.allocate(packetData.remaining()) + recvData.put(packetData) + recvData.rewind() + + assertEquals(buffer, recvData) + } + + @Test + fun testDumpMultipleEnhancedPacketBlocks() { + dumper = PcapNgTcpServerPacketDumper(isSimple = false) + dumper.start() + connectToServer(InetSocketAddress("localhost", PcapNgTcpServerPacketDumper.DEFAULT_PORT)) + + var readBuffer = waitForData() + verifyHeaders(readBuffer) + + val buffer = ByteBuffer.wrap(byteArrayOf(0x01, 0x02, 0x03, 0x04)) + val buffer2 = ByteBuffer.wrap(byteArrayOf(0x05, 0x06, 0x07, 0x08)) + dumper.dumpBuffer(buffer, 0, buffer.limit(), false, null) + dumper.dumpBuffer(buffer2, 0, buffer2.limit(), false, null) + + readBuffer = waitForData() + + val enhancedPacketBlock = PcapNgEnhancedPacketBlock.fromStream(readBuffer) + assertEquals(buffer, ByteBuffer.wrap(enhancedPacketBlock.packetData)) + + if (readBuffer.hasRemaining()) { + val enhancedPacketBlock2 = PcapNgEnhancedPacketBlock.fromStream(readBuffer) + assertEquals(buffer2, ByteBuffer.wrap(enhancedPacketBlock2.packetData)) + } else { + readBuffer = waitForData() + val enhancedPacketBlock2 = PcapNgSimplePacketBlock.fromStream(readBuffer) + assertEquals(buffer2, ByteBuffer.wrap(enhancedPacketBlock2.packetData)) + } + } +}