J'essaye de lire nu Data dans un Swift 4 struct en utilisant la méthode withUnsafeBytes. Le problème

Le paquet UDP réseau a ce 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)

Ensuite, j'ai une extension sur Data qui prend un start et le length d'octets à lire

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

Cela fonctionne correctement lors de la lecture des valeurs une par une:

// 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)

Ensuite, j'ai créé un struct qui représente le paquet entier comme suit

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

Mais lorsque je lis les données directement dans la structure, j'ai quelques problèmes:

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

Je suis censé travailler pour analyser une structure entière comme celle-ci?

5
Jan 28 nov. 2017 à 10:14

3 réponses

Meilleure réponse

La lecture de la structure entière à partir des données ne fonctionne pas car les membres de la structure sont remplis jusqu'à leur limite naturelle. le la disposition de la mémoire de struct XPLBeacon est

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

 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)

Et le remplissage est inséré pour que les membres UInt32 soient aligné sur les adresses mémoire qui sont un multiple de leur taille. C'est également confirmé par

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

(Pour plus d'informations sur l'alignement dans Swift, voir Disposition de type).

Si vous lisez toutes les données dans la structure, les octets sont affectés comme suit

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

Ce qui explique pourquoi major/minorVersion est correct, mais applicationHostId et versionNumber sont faux. La lecture de tous les membres séparément des données est la bonne solution.

4
Martin R 28 nov. 2017 à 08:10

Puisque Swift 3 Data est conforme à RandomAccessCollection, MutableCollection, RangeReplaceableCollection. Vous pouvez donc simplement créer un initialiseur personnalisé pour initialiser vos propriétés de structure comme suit:

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
6
Leo Dabus 12 janv. 2020 à 07:23

Suite à la réponse de Leo Dabus, j'ai créé un constructeur légèrement plus lisible:

extension Data {
    func object<T>(at index: Index) -> T {
        subdata(in: index ..< index.advanced(by: MemoryLayout<T>.size))
            .withUnsafeBytes { $0.load(as: T.self) }
    }
}

struct XPLBeacon {
    var majorVersion: UInt8
    var minorVersion: UInt8
    var applicationHostId: UInt32
    var versionNumber: UInt32

    init(data: Data) {
        var index = data.startIndex
        majorVersion = data.object(at: index)

        index += MemoryLayout.size(ofValue: majorVersion)
        minorVersion = data.object(at: index)

        index += MemoryLayout.size(ofValue: minorVersion)
        applicationHostId = data.object(at: index)

        index += MemoryLayout.size(ofValue: applicationHostId)
        versionNumber = data.object(at: index)
    }
}

Ce qui n’en fait pas partie, c’est bien entendu la vérification de l’exactitude des données. Comme d'autres l'ont mentionné dans les commentaires, cela pourrait être fait soit en ayant une méthode d'initialisation disponible, soit en lançant une erreur.

0
Leo Dabus 12 janv. 2020 à 07:29
47525922