Si le calcul de la somme de contrôle réussit quand un fichier est traité d’un seul bloc, mais échoue lorsqu’il est lu par blocs, le problème vient généralement d’une réinitialisation de l’état du CRC à chaque bloc, et non du réseau du Mac distant. Pour une équipe répartie dans plusieurs régions qui intègre le traitement des fichiers à ses tests continus, figer le fichier d’entrée ne suffit pas : il faut aussi fixer la variante de l’algorithme, le découpage et la représentation du résultat. Nous allons créer en Swift un exemple de validation directement exécutable sur un Mac distant, puis intégrer les mêmes vérifications à un projet iOS.
Définir d’abord le contrat de calcul du CRC-32
Cet article utilise CRC-32/ISO-HDLC : le polynôme en représentation normale est 0x04C11DB7, et l’implémentation réfléchie utilise 0xEDB88320. La valeur initiale et le XOR final valent tous deux 0xFFFFFFFF, avec réflexion en entrée et en sortie. Ne vous contentez pas d’indiquer « CRC32 » dans une interface : les deux parties pourraient choisir des variantes différentes.
La chaîne de référence 123456789 doit donner CBF43926, et une entrée vide doit donner 00000000. Ici, la chaîne est convertie en octets UTF-8, sans saut de ligne. Si vous préparez des échantillons dans le terminal, attention aux commandes qui ajoutent automatiquement un saut de ligne.
Le CRC-32 sert à détecter les altérations accidentelles, pas à garantir l’absence de falsification. Pour vérifier la provenance d’un fichier, validez une signature numérique de confiance : un CRC placé à côté du fichier ne constitue pas une preuve de sécurité.
Le contrat d’échange doit également préciser si le résultat est un texte hexadécimal de huit caractères ou un entier sur quatre octets. Dans le second cas, il faut aussi convenir de l’ordre des octets. La « réflexion » de l’algorithme n’implique pas une transmission en petit-boutiste.
Écrire une implémentation qui conserve son état interne
Enregistrez le code suivant dans crc-check.swift. Cet exemple traite les données bit par bit pour faciliter la vérification des paramètres. Il sert de référence de correction, pas de modèle de performance. Si vous passez ensuite à une implémentation optimisée par table de correspondance en production, réutilisez les tests présentés plus loin.
import Foundation
struct CRC32 {
private var register: UInt32 = 0xFFFFFFFF
mutating func update<S: Sequence>(_ bytes: S)
where S.Element == UInt8 {
for byte in bytes {
register ^= UInt32(byte)
for _ in 0..<8 {
let lowBit = register & 1
register >>= 1
if lowBit != 0 {
register ^= 0xEDB88320
}
}
}
}
var value: UInt32 {
register ^ 0xFFFFFFFF
}
}
func checksum(_ bytes: [UInt8]) -> UInt32 {
var crc = CRC32()
crc.update(bytes)
return crc.value
}
func require(_ condition: Bool, _ name: String) {
if !condition {
fatalError(name)
}
}
let vector = Array("123456789".utf8)
require(checksum(vector) == 0xCBF43926, "reference-vector")
require(checksum([]) == 0, "empty-input")
for split in 0...vector.count {
var crc = CRC32()
crc.update(vector[..<split])
let interim = crc.value
require(interim == checksum(Array(vector[..<split])),
"intermediate-value")
crc.update([])
crc.update(vector[split...])
require(crc.value == 0xCBF43926, "split-invariance")
}
value n’est qu’une vue en lecture seule du registre interne : consulter un résultat intermédiaire ne modifie pas la suite du calcul. Ne modifiez pas le registre dans cette propriété et ne prenez pas le résultat obtenu après le XOR final comme état initial du bloc suivant. Créer une instance par fichier est plus explicite que de proposer une méthode de réinitialisation facile à mal utiliser.
Passer du vecteur de référence aux tests par blocs
Un vecteur de neuf octets ne couvre qu’une petite partie du comportement. Ajoutez ensuite une entrée déterministe contenant toutes les valeurs possibles d’un octet, afin de tester aussi les octets dont le bit de poids fort vaut 1, les octets nuls et les derniers blocs incomplets.
let payload = (0..<4097).map {
UInt8(truncatingIfNeeded: $0)
}
let expected = checksum(payload)
for size in [1, 7, 64, 1024, 4096, 8192] {
var crc = CRC32()
var offset = 0
while offset < payload.count {
let end = min(offset + size, payload.count)
crc.update(payload[offset..<end])
offset = end
}
require(crc.value == expected, "chunk-size-\(size)")
}
var first = CRC32()
first.update(vector)
var second = CRC32()
second.update(payload)
require(first.value == 0xCBF43926, "independent-state")
require(second.value == expected, "second-stream")
Ces tests vérifient que la même implémentation donne un résultat identique quel que soit le découpage. Ils ne suffisent pas, à eux seuls, à prouver que l’algorithme est correct. Il faut donc conserver le vecteur de référence. Lors de l’intégration effective à un protocole, ajoutez aussi des échantillons binaires produits par une implémentation indépendante et dont les résultats ont été vérifiés.
N’affirmez pas que deux entrées différentes doivent nécessairement produire des CRC différents. Le nombre de valeurs possibles est limité et les collisions existent. L’absence de collision sur quelques fichiers aléatoires ne prouve pas la fiabilité de l’implémentation.
Vérifier la gestion des erreurs lors de la lecture des fichiers
Ajoutez la fonction de lecture suivante au même fichier. Elle lit les blocs séquentiellement avec FileHandle et transmet chaque bloc à la même instance, sans charger tout le fichier en mémoire.
func checksumFile(_ url: URL) throws -> UInt32 {
let handle = try FileHandle(forReadingFrom: url)
defer { try? handle.close() }
var crc = CRC32()
while true {
let data = try handle.read(upToCount: 64 * 1024) ?? Data()
if data.isEmpty {
break
}
crc.update(data)
}
return crc.value
}
if CommandLine.arguments.count == 2 {
let url = URL(fileURLWithPath: CommandLine.arguments[1])
let result = try checksumFile(url)
Swift.print(String(format: "%08X", result))
}
La commande xcrun swift crc-check.swift exécute les assertions précédentes. Si vous lui ajoutez le chemin d’un fichier lisible, elle affiche également sa somme de contrôle. Les erreurs de lecture sont propagées au code appelant, au lieu d’être interprétées à tort comme un fichier vide. La couche métier doit journaliser séparément les échecs d’ouverture, les échecs de lecture et les résultats non conformes.
Le fichier lu doit être une copie stable, dont l’écriture est terminée et qui a été fermée. Si un téléchargement modifie encore le fichier au même chemin lorsque vous l’ouvrez séparément pour calculer son CRC, vous risquez de lire un mélange de plusieurs versions. La fin du calcul ne garantit pas non plus qu’aucune écriture n’aura lieu ensuite. Pour la validation, figez d’abord l’entrée, puis comparez sa longueur et son CRC.
Vérifier à qui appartient l’état lors de l’intégration au projet
Placez l’implémentation dans un module accessible aux tests unitaires et conservez quatre catégories de vérifications : vecteurs de référence, invariance par découpage, isolation des instances et propagation des erreurs de fichier. Pour les tests de fichiers, utilisez un répertoire temporaire dédié, créez un fichier vide et un fichier binaire, puis nettoyez le répertoire à la fin.
L’interface iOS doit uniquement afficher la progression et le résultat ; la lecture des fichiers ne doit pas occuper le thread principal. Chaque tâche de transfert doit disposer de sa propre instance de CRC. Les blocs d’une même tâche doivent être traités dans l’ordre du fichier : ne laissez pas des fonctions de rappel concurrentes modifier directement le registre.
Si une tâche est annulée en cours de route, abandonnez son instance. Pour reprendre un transfert interrompu, la seule somme de contrôle finale ne permet pas de poursuivre directement le calcul : il faut relire le préfixe déjà enregistré ou concevoir des points de reprise de l’état interne soumis à des contraintes de version. Dans ce dernier cas, il faut également vérifier la position dans le fichier et son identité ; enregistrer un simple entier ne suffit pas.
Enfin, consignez dans les artefacts de test la version de la chaîne d’outils, la longueur des échantillons, le découpage utilisé et le nom des assertions en échec. Au prochain changement de nœud ou à la prochaine optimisation de l’algorithme, vous pourrez ainsi distinguer une modification des paramètres, un bloc oublié et un partage d’état accidentel, au lieu de retransmettre les fichiers à répétition en espérant que cela fonctionne.
Questions fréquentes
CRC-32 prouve-t-il qu’un fichier téléchargé n’a pas été remplacé ?
Non. CRC-32 détecte les altérations accidentelles, pas les modifications malveillantes. Un attaquant peut remplacer le fichier et sa somme. Utilisez une signature numérique de confiance ou un mécanisme d’authentification à clé adapté au protocole.
Peut-on appliquer XOR aux CRC des blocs pour obtenir celui du fichier ?
Non. Pour une lecture séquentielle, mettez à jour le même état interne et récupérez la valeur finale après le dernier octet. La combinaison de CRC indépendants exige un algorithme spécifique tenant compte de la longueur des blocs.
Louez un Mac mini au rythme de votre projet
Choisissez un nœud physique dédié et payez à la journée, à la semaine, au mois ou au trimestre. Ajoutez du temps en période de forte demande ; la location s’arrête à l’échéance sans renouvellement. La disponibilité réelle est indiquée en temps réel dans la console.