For a C based embedded hardware module, the configuration structure is made up of several fields in a particular layout, e.g. take this 8-byte struct for example:
Offset | Datatype | Field |
---|---|---|
0 | UInt8 | fieldA |
1 | UInt16 | some_value |
3 | UInt32 | another_value |
7 | UInt8 | aByte |
This 8-byte config can be read and written via NFC communication. So there is an Android app which reads this data (as a sequence of 8 bytes) and can write it back, so that the firmware on the embedded hardware module (written in C) can "understand" it.
The task is now to decode the 8-byte sequence of e.g. 12 ab cd 04 fe ff 56 77
(little-endian!) into the decoded values:
Field | Bytes to be decoded | Decoded, human-readable number |
---|---|---|
fieldA | 12 | 0x12 |
some_value | ab cd | 0xCDAB |
another_value | 04 fe ff 56 | 0x56FFFE04 |
aByte | 77 | 0x77 |
Note that this is a Kotlin or Java question, no C question ;)
Now my question is about finding a Kotlin way to decode such a binary struct into the respective values (as shown above), so that the values can be presented to the app user. And, encode the values (after the user would have edited some values) back into the binary structure of 8 bytes.
Note that endianness is also an issue. In general, the target system is a little-endianed ARM and the Android app also runs on a little-endian system, so there might be no issue. However, this is by coincidence, and I would like to make this explicit.
What could be a Kotlin way of decoding/encoding numbers into such bytes, using explicit endian conversion of necessary?
If it was for Python, the struct
library with its pack
and unpack
function are PERFECT for such tasks. But how to do this in Kotlin? I would love to see exactly such functions ...
CodePudding user response:
The best I can think of is to just wrap the byte array in a ByteBuffer
and read it one by one.
Suppose you have:
data class SomeStructure(
val fieldA: UByte,
val someValue: UShort,
val anotherValue: UInt,
val aByte: UByte,
)
You can do:
val byteArray: ByteArray = ....
val buffer = ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN)
val someStruct = SomeStructure(
buffer.get().toUByte(),
buffer.getShort().toUShort(),
buffer.getInt().toUInt(),
buffer.get().toUByte(),
)
println(someStruct)
Note that the get
and getXXX
methods of ByteBuffer
advances the "reading" position, hence mutating the ByteBuffer
, so if you want to re-read the buffer again for whatever reason after creating the SomeStructure
, you should flip
it, or just create a new byte buffer.
You could also make this a secondary constructor of SomeStructure
.
constructor(buffer: ByteBuffer): this(
buffer.get().toUByte(),
buffer.getShort().toUShort(),
buffer.getInt().toUInt(),
buffer.get().toUByte(),
)
This way, you could even support reading data classes with references to SomeStructure
s from byte buffers:
data class SomeOtherStructure(
val struct1: SomeStructure,
val struct2: SomeStructure,
) {
constructor(buffer: ByteBuffer): this(
SomeStructure(buffer), // recall that this advances the current position of the buffer
SomeStructure(buffer)
)
}