Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading Data into a Struct in Swift

Tags:

swift

I'm trying to read bare Data into a Swift 4 struct using the withUnsafeBytes method. The problem

The network UDP packet has this format:

data: 0102 0A00 0000 0B00 0000

01           : 1 byte : majorVersion      (decimal 01)
02           : 1 byte : minorVersion      (decimal 02)
0A00 0000    : 4 bytes: applicationHostId (decimal 10)
0B00 0000    : 4 bytes: versionNumber     (decimal 11)

Then I have an extension on Data that takes a start and the length of bytes to read

extension Data {
    func scanValue<T>(start: Int, length: Int) -> T {
        return self.subdata(in: start..<start+length).withUnsafeBytes { $0.pointee }
    }
}

This works correctly when reading the values one by one:

// correctly read as decimal "1"
let majorVersion: UInt8 = data.scanValue(start: 0, length: 1)

// correctly read as decimal "2"
let minorVersion: UInt8 = data.scanValue(start: 1, length: 1)

// correctly read as decimal "10"
let applicationHostId: UInt32 = data.scanValue(start: 2, length: 4)

// correctly read as decimal "11"
let versionNumber: UInt32 = data.scanValue(start: 6, length: 4)

Then I created a struct that represents the entire packet as follows

struct XPLBeacon {
    var majorVersion: UInt8        // 1 Byte
    var minorVersion: UInt8        // 1 Byte
    var applicationHostId: UInt32  // 4 Bytes
    var versionNumber: UInt32      // 4 Bytes
}

But when I read the data directly into the structure I have some issues:

var beacon: XPLBeacon = data.scanValue(start: 0, length: data.count)

// correctly read as decimal "1"
beacon.majorVersion

// correctly read as decimal "2"
beacon.minorVersion

// not correctly read
beacon.applicationHostId

// not correctly read
beacon.versionNumber

I it supposed to work to parse an entire struct like this?

like image 339
Jan Avatar asked Nov 28 '17 07:11

Jan


2 Answers

Reading the entire structure from the data does not work because the struct members are padded to their natural boundary. The memory layout of struct XPLBeacon is

 A B x x C C C C D D D D

where

 offset    member
  0        A       - majorVersion (UInt8)
  1        B       - minorVersion (UInt8)
  2        x x     - padding
  4        C C C C - applicationHostId (UInt32)
  8        D D D D - versionNumber (UInt32)

and the padding is inserted so that the UInt32 members are aligned to memory addresses which are a multiple of their size. This is also confirmed by

print(MemoryLayout<XPLBeacon>.size) // 12

(For more information about alignment in Swift, see Type Layout).

If you read the entire data into the struct then the bytes are assigned as follows

 01 02 0A 00 00 00 0B 00 00 00
 A  B  x  x  C  C  C  C  D  D  D  D

which explains why major/minorVersion are correct, but applicationHostId and versionNumber are wrong. Reading all members separately from the data is the correct solution.

like image 131
Martin R Avatar answered Oct 11 '22 21:10

Martin R


Since Swift 3 Data conforms to RandomAccessCollection, MutableCollection, RangeReplaceableCollection. So you can simply create a custom initializer to initialise your struct properties as follow:

struct XPLBeacon {
    let majorVersion, minorVersion: UInt8             // 1 + 1 = 2 Bytes
    let applicationHostId, versionNumber: UInt32      // 4 + 4 = 8 Bytes
    init(data: Data) {
        self.majorVersion = data[0]
        self.minorVersion = data[1]
        self.applicationHostId = data
            .subdata(in: 2..<6)
            .withUnsafeBytes { $0.load(as: UInt32.self) }
        self.versionNumber = data
            .subdata(in: 6..<10)
            .withUnsafeBytes { $0.load(as: UInt32.self) }
    }
}

var data = Data([0x01,0x02, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00,0x00])
print(data as NSData)     // "{length = 10, bytes = 0x01020a0000000b000000}\n" <01020a00 00000b00 0000>

let beacon = XPLBeacon(data: data)
beacon.majorVersion       // 1
beacon.minorVersion       // 2
beacon.applicationHostId  // 10
beacon.versionNumber      // 11
like image 6
Leo Dabus Avatar answered Oct 11 '22 21:10

Leo Dabus