Périmètre : robustesse, compatibilité et récupération des fichiers persistants. Ce dossier n’introduit aucun nouveau format produit ; il cherche d’abord à comprendre et tester les formats existants.
1. Sources auditées
KnownFileList.cpp
KnownFile.cpp
PartFile.cpp
DownloadQueue.cpp
ClientCredits.cpp
kademlia/Prefs.cpp
kademlia/routing/RoutingZone.cpp
ServerList.cpp
Preferences.cpp
Opcodes.h
SafeFile.*
Références secondaires :
edonkey-protocol.txt
documentation développeur aMule
analyse MetMedic
2. Cartographie des responsabilités
known.met
→ mémoire des fichiers
*.part.met
→ état de chaque téléchargement
clients.met
→ crédits / clé SecureIdent distante
nodes.dat
→ contacts Kad
server.met
→ serveurs eD2K
preferencesKad.dat
→ Kad ID local
cryptkey.dat
→ clé privée SecureIdent
cancelled.met
→ fichiers annulés mémorisés
3. known.met — headers
Le loader accepte :
MET_HEADER = 0x0E
MET_HEADER_I64TAGS = 0x0F
Le writer moderne écrit :
0x0F
puis :
uint32 record count
4. known.met — records
Chaque entrée est chargée par :
CKnownFile::LoadFromFile()
et rejetée si ses invariants principaux ne sont pas satisfaits.
Le code trace notamment les incohérences :
file size
MD4 master
part-hash count attendu/disponible
5. Sauvegarde known.met
Le writer écrit d’abord le nombre d’éléments de la map, puis peut le corriger si certaines entrées ne doivent pas être sauvegardées.
Condition :
RememberDownloadedFiles
OU
fichier toujours partagé
6. Cadence known.met
CKnownFileList::Process() :
~11 min
entre sauvegardes.
Mesurer l’impact réel sur une très grosse base avant toute optimisation.
7. cancelled.met
Format actuel documenté directement dans le code :
Header
Version
Seed
Count
[HashHash + TagCount + Tags]*
Headers :
ancien = 0x0E
actuel = 0x0F
version = 0x01
8. Conversion cancelled ancienne version
L’ancienne version stockait le vrai hash.
La nouvelle calcule une représentation dérivée de :
Seed || MD4
→ MD5
Le fichier n’expose donc plus simplement le FileHash original.
9. Seed cancelled.met
Si seed = 0 :
nouveau seed pseudo-aléatoire non nul
Le mécanisme n’a pas vocation à être une protection cryptographique robuste du disque ; il évite surtout le stockage direct des hashes réels.
10. part.met — importance
Le .part contient les données.
Le .part.met porte :
identité
taille
hashsets
gaps
priorités
état
métadonnées
Perdre le .part.met peut donc rendre un gros fichier .part difficile à reconstruire correctement.
11. Versions part.met
Constantes actuelles :
PARTFILE_VERSION = 0xE0
PARTFILE_SPLITTEDVERSION = 0xE1
PARTFILE_VERSION_LARGEFILE = 0xE2
Étudier précisément les chemins de compatibilité dans LoadPartFile() avant de documenter le layout byte-à-byte.
13. Backup
Après remplacement réussi :
CopyFile(part.met, part.met.bak)
sous réserve de l’option bDontOverrideBak.
Cette séquence est déjà une petite transaction locale.
14. Échec d’écriture
En cas d’exception :
Abort()
remove(tmp)
return false
Le fichier principal antérieur reste alors préférable à un temporaire incomplet.
15. Récupération startup
DownloadQueue::Init() parcourt :
*.part.met
Si :
PLR_FAILED_METFILE_CORRUPT
il tente :
.part.met.bak
16. Récupération .tmp
Après le scan normal, le code cherche aussi :
*.part.met.tmp
Un temporaire valide peut être resauvegardé comme état principal.
17. Duplicate recovery
Si le temporaire correspond à un partfile déjà chargé :
compare CompletedSize
et peut remplacer l’autre s’il est plus avancé.
Bon exemple d’une récupération fondée sur état utile, pas uniquement timestamp.
18. Gaps
La liste de gaps encode les plages absentes.
Invariants :
start <= end
end < file size
gaps ordonnés
gaps fusionnés si intersections
AddGap() maintient cet ordre/fusion.
19. Gaps et données bufferisées
IsCompleteBD() prend également en compte des données reçues mais pas encore écrites sur disque.
Donc :
complete logique
et :
complete + réellement écrit
peuvent temporairement différer.
20. Crash consistency
Question interne importante :
à quel moment un buffer reçu est marqué comme présent
par rapport à son flush disque et à la sauvegarde part.met ?
À auditer spécifiquement avec scénarios de coupure.
23. Hashsets
Un part.met peut contenir des hashes MD4/AICH.
Invariants :
part count théorique
part count disponible
file size
master hash
Le D07 couvre l’algorithme ; D15 teste la persistance.
24. clients.met
Headers :
CREDITFILE_VERSION_29 = 0x11
CREDITFILE_VERSION = 0x12
Le loader lit la structure adaptée à la version.
25. Backup de crédits
Au chargement, le code inspecte :
clients.met.bak
Si le backup existant est plus gros que le fichier courant, il évite de l’écraser.
Hypothèse implicite :
fichier courant anormalement petit
→ pourrait être tronqué
26. Expiration des crédits
Seuil actuel :
150 jours
Une entrée plus ancienne n’est pas réimportée dans la map active.
Mesurer le volume réel des fichiers avant de considérer ce seuil comme problème.
27. Sauvegarde clients.met
Writer :
version 0x12
count
CreditStruct[]
Il ne sauvegarde que les records ayant au moins un octet uploadé ou downloadé.
28. Cadence crédits
Process() :
~13 min
29. SecureIdent dans crédits
CreditStruct peut conserver :
public key
key size
Lorsqu’une clé est nouvellement vérifiée sur un record qui n’en avait pas, le code peut remettre les vieux totaux à presque zéro pour éviter une appropriation de crédits.
30. cryptkey.dat
Fichier séparé pour la clé privée locale.
Risque majeur :
perte → nouvelle identité SecureIdent
corruption → crypto indisponible / régénération selon code
À sauvegarder avec le profil, mais ne jamais publier.
32. nodes.dat
D11 a couvert la logique réseau.
D15 se concentre sur le format persistant :
ancien count direct
nouveau marker 0 + version
v1/v2/v3
bootstrap edition
33. Writer nodes.dat
La branche sélectionne un échantillon de contacts via :
GetBootstrapContacts(...)
plutôt que de sérialiser naïvement toute la table.
Le fichier est donc un cache sélectionné.
34. Protection contre écrasement prématuré
Si un bootstrap nodes.dat est encore en cours et qu’aucun vrai contact n’est entré dans la routing table, le writer peut refuser d’écraser le bootstrap par un fichier vide.
Bonne logique de récupération.
36. preferencesKad.dat
Structure actuelle très petite :
IP uint32
old port uint16
KadID uint128
legacy tag-count byte on write
Un fichier absent est acceptable :
nouveau KadID
Mais il change l’identité logique Kad.
37. server.met
D10 a déjà documenté :
0x0E / 0xE0
records + tags
.tmp/new/old strategy
Ici, ne pas dupliquer l’étude.
38. preferences.ini
Une partie des anciennes préférences binaires a migré vers INI.
Le format est plus lisible, mais cela n’élimine pas les problèmes de migration :
clé absente
ancienne valeur
valeur hors plage
39. Stratégie commune de robustesse
Pour chaque fichier persistant :
parse
validate version
validate count
validate bounds
construct temporary object
commit object only if valid
Éviter de modifier l’état global avant validation complète.
40. Count × record-size overflow
Tous les formats ayant :
count
doivent être testés pour :
count * minimumRecordSize
avec arithmetic 64-bit sûre.
Le code nodes.dat possède déjà un contrôle de ce type.
41. Tag lengths
Cibles de fuzzing transversales :
string length
blob length
tag count
tag type
unknown tag
64-bit tag
EOF
42. Atomicité
Comparer les stratégies :
part.met → tmp + rename + bak
server.met → new + old
clients.met → backup au load + write direct
known.met → write direct via SafeFile/Commit
Il n’existe pas une stratégie uniforme.
43. Étude M15-5 — matrice de durabilité
Pour chaque fichier :
backup ?
tmp ?
atomic rename ?
write-through ?
validation au load ?
récupération auto ?
Créer un tableau interne de criticité.
44. Criticité proposée pour l’étude
Sans noter/ranker publiquement :
cryptkey.dat
→ identité non reconstructible par le réseau
part.met
→ progression spécifique locale
clients.met
→ historique de crédits
known.met
→ mémoire reconstructible partiellement
nodes.dat/server.met
→ bootstrap reconstructible via réseau/listes
Cette classification sert à choisir les tests de sauvegarde, pas à publier un « score ».
45. MetMedic
L’analyse du corpus montre qu’un outil externe peut lire/modifier known.met et d’autres structures.
Elle souligne aussi le risque d’écrire des tags ou stats non cohérents.
Principe :
read-only inspector avant editor
48. Réparation automatique : risque
Un outil qui « devine » un hash, une taille ou des gaps peut transformer une situation récupérable en état faux.
Toute réparation doit exiger :
preuve suffisante
backup préalable
dry-run
journal exact
49. Migration
Avant toute modification de format interne future, préparer :
reader old
writer old/new
round-trip tests
downgrade behavior
backup
Ne jamais changer un header juste pour « moderniser ».
50. Corruption silencieuse
Le cas le plus dangereux n’est pas toujours le crash.
C’est :
parser accepte
mais état est faux
Les tests doivent donc comparer les invariants après chargement, pas seulement « ne crash pas ».
51. Corpus de fixtures
Créer un répertoire versionné :
tests/fixtures/met/
known/
part/
clients/
nodes/
server/
cancelled/
avec provenance/version documentée.
52. Cross-implementation
Comparer les lecteurs :
eMule
aMule
outils historiques
pour les fichiers réellement partagés entre implémentations.
Une différence peut révéler une extension non standard.
53. Données personnelles
Ces fichiers peuvent révéler :
noms de fichiers
historique de téléchargements
IP/contacts
pseudonymes indirects
crédits
Un rapport ou archive publique doit être nettoyé avant partage.
54. Clés privées
Jamais inclure :
cryptkey.dat
dans un corpus public réel.
Pour les tests, générer une clé synthétique.
55. Conclusion
La persistance eMule est déjà plus robuste qu’un simple « save file » :
versions
tags
backups
temp files
recovery
validation
Mais les stratégies sont hétérogènes et anciennes.
Le travail prioritaire est donc de construire des parsers testés, des fixtures et des scénarios de coupure avant d’envisager toute réécriture.
================================================================================================