Reading Data into a Struct in Swift

Viewed 2447

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?

3 Answers

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
Related