iOS-UUIDs prüfen: Textformat, Versionsregeln und Bytefolge

iOS-UUIDs prüfen: Textformat, Versionsregeln und Bytefolge

iOS-UUIDs prüfen: Textformat, Versionsregeln und Bytefolge

Für denselben Bestelldatensatz liefert die API eine UUID in Großbuchstaben, während der lokale Cache sie in Kleinbuchstaben speichert. Die Duplikaterkennung vergleicht jedoch einfach die Zeichenfolgen. An anderer Stelle wird die UUID binär geschrieben, und beim Einlesen stimmt die Bytefolge der ersten Gruppen nicht mehr. Solche Fehler lassen sich auf einem Cloud-Mac bereits vor dem Build prüfen, ohne einen Simulator zu starten. Zunächst legen feste Swift-Testvektoren den Vertrag für Eingaben, Identitätsvergleiche und Serialisierung fest. Anschließend verwendet das iOS-Projekt dieselben Regeln.

Format und fachliche Regeln zunächst trennen

Eine UUID ist ein 128-Bit-Bezeichner. Ihre übliche Textdarstellung besteht aus fünf Gruppen hexadezimaler Zeichen mit den Längen 8, 4, 4, 4 und 12. Die vier Bindestriche stehen an festen Positionen. Aus diesem Format allein folgt jedoch nicht, dass es sich um eine zufällig erzeugte UUID der Version 4 handelt.

Zunächst sind drei Regeln festzulegen: Sind Großbuchstaben zulässig? Sind führende oder nachgestellte Leerraumzeichen erlaubt? Gibt es eine Versionsbeschränkung? In diesem Artikel sind Groß- und Kleinbuchstaben zulässig; Leerraum, geschweifte Klammern, Darstellungen ohne Bindestriche und Nicht-ASCII-Zeichen werden abgelehnt. Version 4 und die zugehörige Variante werden nur bei Feldern geprüft, die ausdrücklich einen zufälligen Bezeichner verlangen.

„Lässt sich parsen“ beschreibt eine Fähigkeit der Bibliothek. „Darf ins System gelangen“ ist eine Entscheidung des Protokolls. Die Toleranz eines Parsers darf nicht ungeprüft zum API-Vertrag werden.

Auch für die UUID, die ausschließlich aus Nullen besteht, ist eine eigene Entscheidung nötig. Sie kann dem allgemeinen Textformat entsprechen, sollte aber nicht automatisch als Ersatz für „nicht gesetzt“ dienen. Für optionale Felder ist eine explizite Null-Semantik vorzuziehen.

Feste Testvektoren auf dem Cloud-Mac ausführen

Bereiten Sie einen Cloud-Mac mit der vorgesehenen Entwickler-Toolchain vor und speichern Sie den folgenden Inhalt als uuid-contract.swift. Das Skript verwendet ausschließlich Foundation und benötigt weder ein Projekt noch Netzwerkzugriff oder Zufallszahlen. precondition dient in diesem Testskript dazu, bei einem Fehler abzubrechen, nicht zur Verarbeitung externer Eingaben in einer Anwendung.

import Foundation

func parseUUID(_ text: String, requireV4: Bool = false) -> UUID? {
    let bytes = Array(text.utf8)
    guard bytes.count == 36 else { return nil }

    let separators: Set<Int> = [8, 13, 18, 23]
    for (index, byte) in bytes.enumerated() {
        if separators.contains(index) {
            guard byte == 45 else { return nil }
        } else {
            let isHex = (48...57).contains(byte)
                || (65...70).contains(byte)
                || (97...102).contains(byte)
            guard isHex else { return nil }
        }
    }

    guard let value = UUID(uuidString: text) else { return nil }
    if requireV4 {
        let raw = value.uuid
        guard raw.6 >> 4 == 4, raw.8 & 0xC0 == 0x80 else {
            return nil
        }
    }
    return value
}

let valid = "00112233-4455-4677-8899-aabbccddeeff"
let parsed = parseUUID(valid, requireV4: true)!
precondition(parsed.uuidString.lowercased() == valid)
precondition(parseUUID(valid.uppercased()) == parsed)

let invalid = [
    " " + valid,
    valid + "\n",
    "{" + valid + "}",
    valid.replacingOccurrences(of: "-", with: ""),
    valid.replacingOccurrences(of: "a", with: "a")
]
for input in invalid {
    precondition(parseUUID(input) == nil)
}

let otherVersion = "00112233-4455-5677-8899-aabbccddeeff"
precondition(parseUUID(otherVersion) != nil)
precondition(parseUUID(otherVersion, requireV4: true) == nil)

let wrongVariant = "00112233-4455-4677-0899-aabbccddeeff"
precondition(parseUUID(wrongVariant) != nil)
precondition(parseUUID(wrongVariant, requireV4: true) == nil)

let raw = parsed.uuid
let wire: [UInt8] = [
    raw.0, raw.1, raw.2, raw.3,
    raw.4, raw.5, raw.6, raw.7,
    raw.8, raw.9, raw.10, raw.11,
    raw.12, raw.13, raw.14, raw.15
]
precondition(wire == [
    0x00, 0x11, 0x22, 0x33,
    0x44, 0x55, 0x46, 0x77,
    0x88, 0x99, 0xaa, 0xbb,
    0xcc, 0xdd, 0xee, 0xff
])

Führen Sie xcrun swift uuid-contract.swift aus. Bei Erfolg bleibt die Ausgabe leer; bei einem Fehler endet das Skript mit einem Status ungleich null. Die Pipeline muss diesen Status unverändert weitergeben. Protokollieren Sie außerdem xcrun swift --version, damit sich die Prüfung nach Toolchain-Updates wiederholen und vergleichen lässt.

Identitätsvergleiche über einen einzigen Weg abwickeln

Innerhalb der Anwendung sollten möglichst UUID-Werte statt der ursprünglichen API-Zeichenfolgen verglichen werden. Muss ein textbasierter Datenbankschlüssel geschrieben werden, rufen Sie nach erfolgreichem striktem Parsen uuidString.lowercased() auf. So entsteht eine eindeutige Darstellung mit Kleinbuchstaben und Bindestrichen.

Entfernen Sie nicht erst Bindestriche oder führenden und nachgestellten Leerraum, um anschließend das Parsen zu versuchen. Solche „Reparaturen“ verdecken Protokollfehler vorgelagerter Systeme. Außerdem können dadurch zwei Eingaben, die eigentlich abgelehnt werden müssten, auf denselben Schlüssel abgebildet werden. Wenn die Benutzeroberfläche das Einfügen von Werten erlaubt, kann sie einen ausdrücklichen Bereinigungsschritt anbieten. Die API-Schicht darf dieses Verhalten jedoch nicht stillschweigend übernehmen.

Vor der Migration von Altdaten müssen Kollisionen ermittelt werden, die durch die Vereinheitlichung der Groß- und Kleinschreibung entstehen. Unterscheiden sich zwei Datensätze beispielsweise nur in der Schreibweise der UUID-Buchstaben, darf nicht einfach einer überschrieben werden. Prüfen Sie zuerst die fachlichen Zuordnungen, führen Sie die Datensätze anschließend zusammen oder isolieren Sie problematische Einträge und dokumentieren Sie das Migrationsergebnis.

Bei binärer Übergabe die gesamte Bytefolge prüfen

Der obige Testvektor führt die 16 Bytes in der Reihenfolge auf, in der die hexadezimalen Zeichen im UUID-Text stehen. Diese Reihenfolge muss Teil des Übertragungsprotokolls sein. Sie darf nicht von der nativen Ganzzahldarstellung des jeweiligen Rechners abhängen.

Lesen Sie die ersten acht Bytes nicht als native Ganzzahl ein, um sie anschließend direkt wieder auszugeben. Ebenso wenig darf aus der Bezeichnung GUID in einer Schnittstelle geschlossen werden, dass deren Speicherlayout mit diesem Protokoll übereinstimmt. Bei der Übergabe zwischen Programmiersprachen müssen beide Seiten für denselben festen Text exakt dieselbe hexadezimale Bytefolge ausgeben.

Auch das Einlesen braucht ein unabhängiges Testmuster

„Selbst kodieren und anschließend selbst dekodieren“ reicht nicht aus: Machen beide Seiten denselben Fehler, kann der Roundtrip trotzdem erfolgreich sein. Die empfangende Seite sollte direkt das feste Muster aus 16 Bytes verwenden und per Assertion prüfen, dass der rekonstruierte Text dem ursprünglichen Testvektor entspricht. Zusätzlich muss getestet werden, dass Eingaben mit 15 und 17 Bytes abgelehnt werden. Weder Auffüllen mit Nullen noch Abschneiden ist zulässig.

Die Prüfungen in den tatsächlichen Parsing-Pfad einbinden

Das Skript prüft eine beispielhafte Umsetzung der Regeln. Für den produktiven Einsatz gehört die Parsing-Funktion in ein gemeinsames Modul, das von der API-Dekodierung, den Lese- und Schreibzugriffen auf den Cache sowie den Importwerkzeugen einheitlich aufgerufen wird. Die zugehörigen Testvektoren gehören in das Test-Target. So wird verhindert, dass das Prüfskript besteht, während die Anwendung weiterhin einen anderen, toleranteren Parsing-Pfad verwendet.

Ergänzen Sie vier Gruppen von Testfällen: Gültige Schreibweisen mit Groß- und Kleinbuchstaben müssen dieselbe Identität ergeben; unzulässige Zeichen und Leerraum müssen abgelehnt werden; Versionsbeschränkungen dürfen nur für die dafür vorgesehenen Felder gelten; binäre Testmuster müssen Byte für Byte übereinstimmen. In jeder Gruppe ist das Rückgabeergebnis zu prüfen. Die Feststellung „kein Absturz“ genügt nicht.

Fehlerprotokolle dürfen nur den Feldnamen, die Fehlerkategorie und die Testfallnummer enthalten, keine echten fachlichen Bezeichner. Führen Sie diese Vertragsprüfungen nach Toolchain-Updates, beim Austausch serverseitiger Serialisierungsbibliotheken und vor dem Import historischer Daten zunächst erneut aus. Einen Fehler gezielt der Textebene, den fachlichen Regeln oder der Bytefolge zuzuordnen, ist direkter, als immer wieder zwischen Cache und API nach der Ursache zu suchen.

Häufig gestellte Fragen

Muss jede UUID die Version 4 haben?

Nein. Prüfen Sie Version 4 nur, wenn der Schnittstellenvertrag sie verlangt. Allgemeines Parsing und erlaubte Versionen bleiben getrennte Regeln.

Sind UUIDs mit anderer Großschreibung unterschiedliche Kennungen?

Nein. Parsen Sie gültige Werte und verwenden Sie eine einheitliche Kleinschreibung mit Bindestrichen als Schlüssel. Die Originalschreibweise dient nur der Diagnose.

Der nächste Build auf einem exklusiven Mac.

Wählen Sie einen Cloud-Mac ohne Virtualisierung und behalten Sie Ihren gewohnten Xcode- und CI-Workflow bei. Konfigurieren Sie die Maschine passend zu Ihrer Arbeitslast; den aktuellen Verfügbarkeitsstatus sehen Sie live in der Konsole.

Cloud-Mac jetzt mieten

Den nächsten Build auf einer exklusiven physischen Maschine ausführen.

Behalten Sie Ihre gewohnten Befehle und CI-Abläufe bei und wählen Sie einen Cloud-Mac passend zu Ihrer Arbeitslast.