From ad39a47aa889327eccd0d5cccb0fa321bc4599e6 Mon Sep 17 00:00:00 2001 From: Jason Ernst Date: Wed, 7 Aug 2024 23:04:24 -0700 Subject: [PATCH] WiP: txtfile packet dumper + start of tests --- .../filedumper/TextFilePacketDumper.kt | 92 +++++++++++++++++++ .../filedumper/TestTextFilePacketDumper.kt | 79 ++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 packetdumper/src/main/kotlin/com/jasonernst/packetdumper/filedumper/TextFilePacketDumper.kt create mode 100644 packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestTextFilePacketDumper.kt diff --git a/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/filedumper/TextFilePacketDumper.kt b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/filedumper/TextFilePacketDumper.kt new file mode 100644 index 0000000..9256e08 --- /dev/null +++ b/packetdumper/src/main/kotlin/com/jasonernst/packetdumper/filedumper/TextFilePacketDumper.kt @@ -0,0 +1,92 @@ +package com.jasonernst.packetdumper.filedumper + +import com.jasonernst.packetdumper.EtherType +import com.jasonernst.packetdumper.stringdumper.StringPacketDumper +import org.slf4j.LoggerFactory +import java.io.BufferedWriter +import java.io.File +import java.io.IOException +import java.nio.ByteBuffer +import java.time.LocalDateTime +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Dumps buffers into hexdump text files that can be imported by wireshark. The format is as follows: + * + * offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline + * offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline + * + * offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline + * offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline + * + * Each block of offsets is separated by a blank newline, and represents a new packet / dump. When + * the new dump starts, the offset can restart at 0. + */ +class TextFilePacketDumper(private val path: String, private val name: String) : AbstractFilePacketDumper() { + private val logger = LoggerFactory.getLogger(javaClass) + private val stringDumper = StringPacketDumper() + + private val isOpen = AtomicBoolean(false) + override lateinit var filename: String + private lateinit var file: File + private lateinit var bufferedWriter: BufferedWriter + private var loggedError = false + + override fun open() { + if (isOpen.get()) { + logger.error("Trying to open a file that is already open") + return + } + filename = "$path/${name}_${LocalDateTime.now()}.dump" + file = File(filename) + bufferedWriter = file.bufferedWriter() + logger.debug("TextFilePacketDumper opened file $filename") + isOpen.set(true) + } + + override fun close() { + if (!isOpen.get()) { + logger.error("Trying to close a file that is already closed") + return + } + try { + bufferedWriter.flush() + } catch (e: Exception) { + logger.error("Error flushing dump file", e) + } + try { + bufferedWriter.close() + } catch (e: Exception) { + logger.error("Error closing dump file", e) + } + isOpen.set(false) + } + + override fun dumpBuffer( + buffer: ByteBuffer, + offset: Int, + length: Int, + addresses: Boolean, + etherType: EtherType?, + ) { + if (!isOpen.get()) { + if (!loggedError) { + logger.error("Trying to dump to a file that is not open") + loggedError = true + } + return + } + val output = stringDumper.dumpBufferToString(buffer, offset, length, addresses, etherType) + logger.debug("Intermediary output: $output") + try { + bufferedWriter.write(output) + bufferedWriter.write("\n") + bufferedWriter.flush() + } catch (e: IOException) { + if (!loggedError) { + logger.error("Error writing to dump file", e) + loggedError = true + } + } + } +} diff --git a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestTextFilePacketDumper.kt b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestTextFilePacketDumper.kt new file mode 100644 index 0000000..479cd2c --- /dev/null +++ b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestTextFilePacketDumper.kt @@ -0,0 +1,79 @@ +package com.jasonernst.packetdumper.filedumper + +import com.jasonernst.packetdumper.EtherType +import com.jasonernst.packetdumper.stringdumper.StringPacketDumper +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.slf4j.LoggerFactory +import java.io.File +import java.nio.ByteBuffer + +class TestTextFilePacketDumper { + private val logger = LoggerFactory.getLogger(javaClass) + private val dumper = TextFilePacketDumper("/tmp", "test") + + /** + * Dumps a buffer to a file using the dumper, closes the file. Opens it back up and reads the + * buffer back and returns it. + * + * If the addresses flag is true, strip the addresses before returning the buffer. + * If the etherType is not null, strip the dummy ethernet header before returning the buffer. + * + * If either of those two things are present during the write, but not present during the read, + * throw an Exception. + */ + private fun writeReadBuffer( + buffer: ByteBuffer, + addresses: Boolean = false, + etherType: EtherType? = null, + ): ByteBuffer { + dumper.dumpBuffer(buffer, 0, buffer.limit(), addresses, etherType) + dumper.close() + + // re-open the file + val file = File(dumper.filename) + val fileLines = file.readLines() + val readBuffer = ByteBuffer.wrap(fileLines.joinToString().toByteArray()) + + if (etherType != null) { + TODO() // need to strip the dummy ethernet header + } + + if (addresses) { + TODO() // need to strip them + } + + return readBuffer + } + + @AfterEach fun tearDown() { + // just in case a test fails, we want to make sure the file is closed + dumper.close() + + // delete the file created for the test to cleanup + try { + // File(dumper.filename).delete() + logger.debug("Deleted file ${dumper.filename}") + } catch (e: Exception) { + // ignore + } + } + + @Test fun testOpenClose() { + dumper.open() + dumper.close() + } + + @Disabled("This is broken atm") + @Test fun testDumpBuffer() { + dumper.open() + val stringDumper = StringPacketDumper() + val buffer = ByteBuffer.wrap("Hello, World!".toByteArray()) + logger.debug("write buffer: ${stringDumper.dumpBufferToString(buffer, 0, buffer.limit())}") + val readBuffer = writeReadBuffer(buffer) + logger.debug("read buffer: ${stringDumper.dumpBufferToString(readBuffer, 0, readBuffer.limit())}") + assertEquals(buffer, readBuffer) + } +}