Acessing structs from Swift

My latest app communicates, via TCP, with an embedded device.
The embedded device is programmed in C and sends my Swift app data encapsulated in "messages" which are actually structures defined like this:
Code Block typedef struct _MSG_TYPE
{
uint16_t sig;
uint16_t len; // Total length of message, include header and playload;
uint16_t type;
uint32_t data; // 32 Bits of data.
} MSG_t;

In my Swit app they arrive via NWConnection, and are sent to my controller delegate as Data (That's the swift type called Data, not just the general term 'data')

So me question for you harties is this:
How can I unpack this 'Data' into something resembling my C struct - and acess it's members by name.

In Pseudo code I'd like to do something like:
Code Block
if let myData = data {
myStruct = <cast myData.bytes onto a struct >
if myStruct.sig == CORRECT_MSG_SIG {
// do soemthig with the message...
}
}

So is this possible? Is it the wrong approach? Is there a better approach?

Replies

Generally, defining a binary data format using C-struct seems to be a bad idea.

Please check how MSG_t is layout in the byte stream.

Format 1:
Code Block
+--+--+--+--+--+--+--+--+--+--+--+--+--+--
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|...
+--+--+--+--+--+--+--+--+--+--+--+--+--+--
|sig |len |type |data |
+-----+-----+-----+-----------+

Or

Format 2:
Code Block
+--+--+--+--+--+--+--+--+--+--+--+--+--+--
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|...
+--+--+--+--+--+--+--+--+--+--+--+--+--+--
|sig |len |type | |data |
+-----+-----+-----+-----+-----------+

Many compiles put two-byte padding after type, to align data to 32-bit boundary. But that depends on the compiler, CPU architecture or compiling flags.
But in communications, people do not like putting unused bytes.


You also need to care about endianness.

In communication within the same device, you can expect all multi-byte data have the same endianness, so you can program your code using CPU-endian.

But in TCP communications, the two nodes on the internet may have different CPU-endian. And there are many people who prefers big-endian for binary data format on the internet, as you know network order, even if the CPU is little-endian.


So, if you mean this as including cast myData.bytes onto a struct, it is a wrong approach.

The first thing you need is defining the binary data format without using C-struct, specifying endianness explicitly.
(Also specify paddings explicitly, if exists.)

In the example below I assume something like this:
Code Block
offset|number of bytes|endianness |field name
------+---------------|-------------|----------
+0| 2|little endian|sig
+2| 2|little endian|len
+4| 2|little endian|type
+6| 4|little endian|data
------+---------------|-------------|----------


With the data format like this, you can write some code as follows:
Code Block
class DataReader {
var data: Data
var offset: Int = 0
init(data: Data) {
self.data = data
}
func readLittleEndian<T: FixedWidthInteger>()->T {
var rawValue: T = 0
_ = withUnsafeMutableBytes(of: &rawValue) {rawBufPtr in
data.copyBytes(to: rawBufPtr, from: offset...)
}
offset += MemoryLayout<T>.size
return T(littleEndian: rawValue)
}
}
//Sample data
let data: Data? = Data([
0x12, 0x34,
0x56, 0x78,
0x9A, 0xBC,
0xDE, 0xF0, 0x12, 0x34])
if let myData = data {
var myStruct = MSG_t()
let reader = DataReader(data: myData)
myStruct.sig = reader.readLittleEndian()
myStruct.len = reader.readLittleEndian()
myStruct.type = reader.readLittleEndian()
myStruct.data = reader.readLittleEndian()
print(String(format: "%04X", myStruct.sig))
print(String(format: "%04X", myStruct.len))
print(String(format: "%04X", myStruct.type))
print(String(format: "%08X", myStruct.data))
if myStruct.sig == CORRECT_MSG_SIG {
// do soemthig with the message...
}
}

(Assuming MSG_t is defined in C-style header and imported into Swift.)
So, instead of answering my question you just lecture me on data packing?
Nice!