diff --git a/build.gradle.kts b/build.gradle.kts index 15e4a5a..9b268fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } group = "io.github.froks" -version = "0.2.1" +version = "0.2.2" repositories { mavenLocal() @@ -19,6 +19,7 @@ repositories { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("com.willowtreeapps.assertk:assertk:0.28.1") } kotlin { diff --git a/src/main/kotlin/dltcore/DltExtensions.kt b/src/main/kotlin/dltcore/DltExtensions.kt index 842b39f..0268b70 100644 --- a/src/main/kotlin/dltcore/DltExtensions.kt +++ b/src/main/kotlin/dltcore/DltExtensions.kt @@ -1,6 +1,9 @@ package dltcore -public fun String.asIntValue(): Int { +public typealias IdInt = Int +public typealias IdString = String + +public fun IdString.asIntValue(): Int { if (this.length > 4) { throw IllegalArgumentException("AppId '$this' may not be longer than 4 bytes") } @@ -13,7 +16,7 @@ public fun String.asIntValue(): Int { return value } -public fun Int.asStringValue(): String = +public fun IdInt.asStringValue(): String = String( byteArrayOf( if (this and 0xFF000000.toInt() == 0) ' '.code.toByte() else (this and 0xFF000000.toInt() shr 24).toByte(), diff --git a/src/main/kotlin/dltcore/DltObjects.kt b/src/main/kotlin/dltcore/DltObjects.kt index 56230ac..8e6969f 100644 --- a/src/main/kotlin/dltcore/DltObjects.kt +++ b/src/main/kotlin/dltcore/DltObjects.kt @@ -99,7 +99,7 @@ public class DltMessageV1( public class DltStorageHeaderV1( public val timestampEpochSeconds: Long, // 4 unsigned byte in V1 public val timestampMicroseconds: Int, // microseconds in V1, Nanoseconds in V2 - public val ecuId: Int, // 4 chars + public val ecuId: IdInt, // 4 chars ) : DltStorageHeader { public override val ecuIdText: String get() = ecuId.asStringValue() @@ -267,8 +267,8 @@ public enum class MessageTypeInfo(public val type: MessageType, public val value public class DltExtendedHeaderV1( public val msin: Byte, // message info public val noar: Byte, // number of arguments - public val apid: Int, // application id - public val ctid: Int, // context id + public val apid: IdInt, // application id + public val ctid: IdInt, // context id ) : DltExtendedHeader { public fun write(bb: BinaryOutputStream) { bb.order(ByteOrder.BIG_ENDIAN) @@ -354,7 +354,7 @@ public class DltPayloadV1( public fun write(bb: BinaryOutputStream) { bb.order(byteOrder) - bb.put(data) + bb.writeArray(data) } @@ -608,7 +608,6 @@ public class DltPayloadArgumentNumber( } } - override fun toString(): String { - return number.toString() - } + override fun toString(): String = + number.toString() } diff --git a/src/main/kotlin/dltcore/DltParser.kt b/src/main/kotlin/dltcore/DltParser.kt index 47cd8d8..6fe98d5 100644 --- a/src/main/kotlin/dltcore/DltParser.kt +++ b/src/main/kotlin/dltcore/DltParser.kt @@ -3,6 +3,7 @@ package dltcore import library.BinaryInputStream import library.ByteOrder import library.LargeFileByteBufferInputStream +import library.ParseException import java.nio.file.Path import kotlin.io.path.fileSize @@ -18,7 +19,10 @@ public data class DltReadStatus( val error: Exception?, ) -private class DltMessageIterator(val buffer: BinaryInputStream, val totalSize: Long?) : Iterator { +private class DltMessageIterator( + private val buffer: BinaryInputStream, + private val totalSize: Long? +) : Iterator { private var index: Long = 0 private var successCount: Long = 0 private var errorCount: Long = 0 @@ -48,16 +52,18 @@ private class DltMessageIterator(val buffer: BinaryInputStream, val totalSize: L val result = try { val magic = findFirstValidMagic() val version = DltStorageVersion.getByMagic(magic) - parseDltMessage(buffer, version) - } catch (e: RuntimeException) { + val msg = parseDltMessage(buffer, version) + successCount++ + msg + } catch (e: Exception) { errorCount++ - RuntimeException( + ParseException( + buffer.position(), "Error while parsing message at file position ${buffer.position()}: ${e.message}", e ) } - successCount++ val progress = if (totalSize != null) { buffer.position().toFloat() / totalSize.toFloat() } else { @@ -75,7 +81,7 @@ private class DltMessageIterator(val buffer: BinaryInputStream, val totalSize: L error = result as? Exception, ) } - throw RuntimeException("No more data available, but next() was called on iterator") + throw IllegalStateException("No more data available, but next() was called on iterator") } } diff --git a/src/main/kotlin/library/BinaryOutputStream.kt b/src/main/kotlin/library/BinaryOutputStream.kt index e1dcb6e..cb1d4eb 100644 --- a/src/main/kotlin/library/BinaryOutputStream.kt +++ b/src/main/kotlin/library/BinaryOutputStream.kt @@ -2,10 +2,11 @@ package library public interface BinaryOutputStream { public fun order(order: ByteOrder) - public fun put(data: ByteArray) public fun writeByte(value: Byte) public fun writeShort(value: Short) public fun writeInt(value: Int) public fun writeLong(value: Long) + public fun writeArray(data: ByteArray) + public fun position(): Long } diff --git a/src/main/kotlin/library/ByteBufferBinaryOutputStream.kt b/src/main/kotlin/library/ByteBufferBinaryOutputStream.kt index 725c31d..a5f0c79 100644 --- a/src/main/kotlin/library/ByteBufferBinaryOutputStream.kt +++ b/src/main/kotlin/library/ByteBufferBinaryOutputStream.kt @@ -3,27 +3,37 @@ package library import java.nio.ByteBuffer public class ByteBufferBinaryOutputStream(private val buffer: ByteBuffer) : BinaryOutputStream { + private var position: Long = 0 + override fun order(order: ByteOrder) { buffer.order(order.asByteBufferByteOrder()) } - override fun put(data: ByteArray) { - buffer.put(data) - } - override fun writeByte(value: Byte) { buffer.put(value) + position += 1 } override fun writeShort(value: Short) { buffer.putShort(value) + position += 2 } override fun writeInt(value: Int) { buffer.putInt(value) + position += 4 } override fun writeLong(value: Long) { buffer.putLong(value) + position += 8 } + + override fun writeArray(data: ByteArray) { + buffer.put(data) + position += data.size + } + + override fun position(): Long = + position } diff --git a/src/main/kotlin/library/ParseException.kt b/src/main/kotlin/library/ParseException.kt new file mode 100644 index 0000000..7d64047 --- /dev/null +++ b/src/main/kotlin/library/ParseException.kt @@ -0,0 +1,3 @@ +package library + +public class ParseException(public val position: Long, msg: String, e: Exception) : RuntimeException(msg, e) diff --git a/src/test/kotlin/dltcore/DltExtensionsTest.kt b/src/test/kotlin/dltcore/DltExtensionsTest.kt new file mode 100644 index 0000000..61aaa6f --- /dev/null +++ b/src/test/kotlin/dltcore/DltExtensionsTest.kt @@ -0,0 +1,24 @@ +package dltcore + +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +class DltExtensionsTest { + @Test + fun `test int to string`() { + assertThat(0x30313233.asStringValue()).isEqualTo("0123") + assertThat(0x30313200.asStringValue()).isEqualTo("012") + assertThat(0x30310000.asStringValue()).isEqualTo("01") + assertThat(0x30000000.asStringValue()).isEqualTo("0") + assertThat(0x00000000.asStringValue()).isEqualTo("") + } + + @Test + fun `test string to int`() { + assertThat("1234".asIntValue()).isEqualTo(0x31323334) + assertThat("123".asIntValue()).isEqualTo(0x31323300) + assertThat("12".asIntValue()).isEqualTo(0x31320000) + assertThat("1".asIntValue()).isEqualTo(0x31000000) + } +} \ No newline at end of file diff --git a/src/test/kotlin/library/ByteBufferBinaryInputStreamTest.kt b/src/test/kotlin/library/ByteBufferBinaryInputStreamTest.kt new file mode 100644 index 0000000..ee82ae5 --- /dev/null +++ b/src/test/kotlin/library/ByteBufferBinaryInputStreamTest.kt @@ -0,0 +1,151 @@ +package library + +import assertk.assertFailure +import assertk.assertThat +import assertk.assertions.hasClass +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isTrue +import org.junit.Test +import java.nio.BufferUnderflowException + +class ByteBufferBinaryInputStreamTest { + @Test + fun `test byte`() { + ByteBufferBinaryInputStream(createSequencedByteBuffer(4)).let { + it.order(ByteOrder.BIG_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readByte()).isEqualTo(0x00) + assertThat(it.readByte()).isEqualTo(0x01) + assertThat(it.readByte()).isEqualTo(0x02) + assertThat(it.readByte()).isEqualTo(0x03) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readByte() + }.hasClass(BufferUnderflowException::class) + } + + ByteBufferBinaryInputStream(createSequencedByteBuffer(4)).let { + it.order(ByteOrder.LITTLE_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readByte()).isEqualTo(0x00) + assertThat(it.readByte()).isEqualTo(0x01) + assertThat(it.readByte()).isEqualTo(0x02) + assertThat(it.readByte()).isEqualTo(0x03) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readByte() + }.hasClass(BufferUnderflowException::class) + } + } + + @Test + fun `test short`() { + ByteBufferBinaryInputStream(createSequencedByteBuffer(4)).let { + it.order(ByteOrder.BIG_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readShort()).isEqualTo(0x0001) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(2) + assertThat(it.readShort()).isEqualTo(0x0203) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readShort() + }.hasClass(BufferUnderflowException::class) + } + + ByteBufferBinaryInputStream(createSequencedByteBuffer(4)).let { + it.order(ByteOrder.LITTLE_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readShort()).isEqualTo(0x0100) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(2) + assertThat(it.readShort()).isEqualTo(0x0302) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readShort() + }.hasClass(BufferUnderflowException::class) + } + } + + @Test + fun `test int`() { + ByteBufferBinaryInputStream(createSequencedByteBuffer(8)).let { + it.order(ByteOrder.BIG_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readInt()).isEqualTo(0x00010203) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(4) + assertThat(it.readInt()).isEqualTo(0x04050607) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readInt() + }.hasClass(BufferUnderflowException::class) + } + + ByteBufferBinaryInputStream(createSequencedByteBuffer(8)).let { + it.order(ByteOrder.LITTLE_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readInt()).isEqualTo(0x03020100) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(4) + assertThat(it.readInt()).isEqualTo(0x07060504) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readInt() + }.hasClass(BufferUnderflowException::class) + } + } + + @Test + fun `test long`() { + ByteBufferBinaryInputStream(createSequencedByteBuffer(16)).let { + it.order(ByteOrder.BIG_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readLong()).isEqualTo(0x0001020304050607) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(8) + assertThat(it.readLong()).isEqualTo(0x08090a0b0c0d0e0f) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readLong() + }.hasClass(BufferUnderflowException::class) + } + + ByteBufferBinaryInputStream(createSequencedByteBuffer(16)).let { + it.order(ByteOrder.LITTLE_ENDIAN) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(0) + assertThat(it.readLong()).isEqualTo(0x0706050403020100) + assertThat(it.hasRemaining()).isTrue() + assertThat(it.position()).isEqualTo(8) + assertThat(it.readLong()).isEqualTo(0x0f0e0d0c0b0a0908) + assertThat(it.hasRemaining()).isFalse() + assertFailure { + it.readLong() + }.hasClass(BufferUnderflowException::class) + } + } + + @Test + fun `test array`() { + ByteBufferBinaryInputStream(createSequencedByteBuffer(64)).let { + assertThat(it.readArray(32)).isEqualTo(createSequencedByteArray(32)) + assertThat(it.position()).isEqualTo(32) + assertThat(it.readArray(16)).isEqualTo(createSequencedByteArray(16, 32)) + assertThat(it.position()).isEqualTo(48) + assertThat(it.readArray(16)).isEqualTo(createSequencedByteArray(16, 48)) + assertThat(it.position()).isEqualTo(64) + assertFailure { + it.readArray(1) + }.hasClass(BufferUnderflowException::class) + } + } +} diff --git a/src/test/kotlin/library/ByteBufferBinaryOutputStreamTest.kt b/src/test/kotlin/library/ByteBufferBinaryOutputStreamTest.kt new file mode 100644 index 0000000..ee520eb --- /dev/null +++ b/src/test/kotlin/library/ByteBufferBinaryOutputStreamTest.kt @@ -0,0 +1,48 @@ +package library + +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test +import java.nio.ByteBuffer + +class ByteBufferBinaryOutputStreamTest { + @Test + fun `test big endian`() { + val bb = ByteBuffer.allocate(10000) + val length = ByteBufferBinaryOutputStream(bb).let { + it.order(ByteOrder.BIG_ENDIAN) + it.writeByte(0x00) + it.writeShort(0x0001) + it.writeInt(0x00010203) + it.writeLong(0x0001020304050607) + assertThat(it.position()).isEqualTo(1+2+4+8) + it.writeArray(createSequencedByteArray(32)) + assertThat(it.position()).isEqualTo(1+2+4+8+32) + it.position().toInt() + } + val data = ByteArray(length) + bb.position(0) + bb.get(data, 0, data.size) + assertThat(data).isEqualTo("000001000102030001020304050607".decodeHex() + createSequencedByteArray(32)) + } + + @Test + fun `test little endian`() { + val bb = ByteBuffer.allocate(10000) + val length = ByteBufferBinaryOutputStream(bb).let { + it.order(ByteOrder.LITTLE_ENDIAN) + it.writeByte(0x00) + it.writeShort(0x0001) + it.writeInt(0x00010203) + it.writeLong(0x0001020304050607) + assertThat(it.position()).isEqualTo(1+2+4+8) + it.writeArray(createSequencedByteArray(32)) + assertThat(it.position()).isEqualTo(1+2+4+8+32) + it.position().toInt() + } + val data = ByteArray(length) + bb.position(0) + bb.get(data, 0, data.size) + assertThat(data).isEqualTo("000100030201000706050403020100".decodeHex() + createSequencedByteArray(32)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/library/Helper.kt b/src/test/kotlin/library/Helper.kt new file mode 100644 index 0000000..d8c2742 --- /dev/null +++ b/src/test/kotlin/library/Helper.kt @@ -0,0 +1,22 @@ +package library + +import java.nio.ByteBuffer + +fun createSequencedByteBuffer(length: Int): ByteBuffer = + ByteBuffer.wrap(createSequencedByteArray(length)) + +fun createSequencedByteArray(length: Int, valueOffset: Byte = 0): ByteArray { + val array = ByteArray(length) + for (i in 0 until length) { + array[i] = (valueOffset + i.toByte()).toByte() + } + return array +} + +fun String.decodeHex(): ByteArray { + val s = this.replace(" ", "") + check(s.length % 2 == 0) { "String must have an even length" } + return s.chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() +}