Geek++ · Dossier 15 / 25

Audit des formats persistants et de la mémoire locale

Étude technique publique · 1,475 mots · ≈ 7 min de lecture attentive

Étude intégraleSans réflexion CODEXSources & constantes conservées
Mémoire locale
known.met
part.met
clients.met
server.met
nodes.dat

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.

================================================================================================


Dossier 15 terminé

Audit des formats persistants et de la mémoire locale

← Dossier 14Dossier 16 →