0. Résumé exécutif
Ce dossier est volontairement transversal.
Les dossiers précédents ont étudié séparément :
discovery
connectivity
handshake
queue
blocks
integrity
SX
A4AF
servers
Kad
D18 montre que tous ces mécanismes agissent sur le même CUpDownClient au cours du temps.
La découverte centrale est architecturale :
un pair n’a pas un état unique ; plusieurs machines d’état indépendantes coexistent.
Dans ClientStateDefs.h :
EUploadState
EDownloadState
EChatState
EKadState
EConnectingStateLe même client peut donc être simultanément :
DownloadState = DS_ONQUEUE
UploadState = US_ONUPLOADQUEUE
ConnectingState = CCS_NONE
KadState = KS_NONE
ou encore être en train de télécharger chez nous pendant que nous attendons chez lui.
Une grande partie de la complexité historique d’eMule vient de l’orchestration entre ces états, plus que du wire protocol lui-même.
1. États upload
ClientStateDefs.h :
enum EUploadState : uint8
{
US_UPLOADING,
US_ONUPLOADQUEUE,
US_CONNECTING,
US_BANNED,
US_NONE
};Ces états concernent :
ce que le pair fait chez NOUS
Ils ne décrivent pas notre téléchargement depuis lui.
2. États download
La branche contient :
DS_DOWNLOADING
DS_ONQUEUE
DS_CONNECTED
DS_CONNECTING
DS_WAITCALLBACK
DS_WAITCALLBACKKAD
DS_REQHASHSET
DS_NONEEDEDPARTS
DS_TOOMANYCONNS
DS_TOOMANYCONNSKAD // unused since 0.49b
DS_LOWTOLOWIP
DS_BANNED
DS_ERROR
DS_NONE
DS_REMOTEQUEUEFULL // not used yet, except in statisticsLe dernier confirme le constat D09 :
DS_REMOTEQUEUEFULL
est pratiquement fossile, alors que l’état remote queue full est suivi par ailleurs.
3. États de connexion
CCS_NONE
CCS_DIRECTTCP
CCS_DIRECTCALLBACK
CCS_KADCALLBACK
CCS_SERVERCALLBACK
CCS_PRECONDITIONSCe champ décrit comment la tentative actuelle est menée.
Il est donc orthogonal au DownloadState.
4. États Kad
FWCHECK
BUDDY
UDP firewall check
Un même CUpDownClient peut participer à un rôle Kad qui n’a rien à voir avec un téléchargement de fichier.
5. Cycle nominal
Modèle de référence :
SOURCE DISCOVERED
→ admitted
→ AskForDownload
→ TryToConnect
→ handshake
→ File Request
→ Part Status / Hashsets
→ StartUploadReq
→ ONQUEUE
→ periodic reask
→ ACCEPTUPLOADREQ
→ DOWNLOADING
→ Block Requests
→ Data
→ verification
→ queue / NNP / disconnect / completion
6. Admission
Les canaux D01 aboutissent au pipeline d’admission.
Objectifs :
dédoublonner
éviter IP invalides / filtrées
vérifier source limit
éviter DeadSource
associer A4AF si client déjà connu
tester compatibilité crypt
Une source reçue du réseau n’est donc pas automatiquement créée comme nouveau pair.
7. Provenance à conserver
D01 et D08 montrent deux niveaux :
source_from = SERVER / KAD / SX / PASSIVE / LINK
mais dans le cas SX :
provider précis
est actuellement perdu après admission.
Pour D18, la provenance idéale d’une source doit survivre aux transitions.
8. Avant connexion : choix du fichier
A4AF peut signifier que le pair est connu pour :
A
B
C
mais m_reqfile n’en désigne qu’un.
La décision du fichier précède ou accompagne la tentative de connexion.
Il faut donc journaliser :
active_file_at_connect
alternative_files
reason_for_choice
9. TryToConnect() comme machine de routage de connexion
Le commentaire source décrit plusieurs scénarios.
Les chemins réellement matérialisés par EConnectingState sont :
DIRECTTCP
DIRECTCALLBACK
KADCALLBACK
SERVERCALLBACK
PRECONDITIONS
Le code possède également des early exits :
déjà connecté
déjà connecting
pair invalide
ban/filter
incompatibilité crypt
10. Direct TCP
Chemin idéal :
connect(remote IP, remote TCP port)
Puis :
ConnectionEstablished()
→ Hello
La réussite TCP ne signifie pas encore que le fichier existe chez le pair.
11. Direct UDP Callback
Conditions générales issues de D02/D11/D12 :
pair TCP firewalled
Kad/direct callback capability
Kad UDP port utilisable
connect options compatibles
Paquet :
OP_DIRECTCALLBACKREQ
avec :
notre TCP port
notre UserHash
connect options
Le pair est invité à ouvrir TCP vers nous.
12. Server Callback
Si le pair LowID appartient au serveur auquel nous sommes connecté :
CCS_SERVERCALLBACK
OP_CALLBACKREQUEST
Le serveur doit ensuite envoyer au LowID :
OP_CALLBACKREQUESTED
D10/D17 couvrent la partie serveur.
13. Kad Callback
Si le contexte serveur n’est pas utilisable mais que Kad/Buddy l’est :
CCS_KADCALLBACK
le moteur de connexion utilise la logique Kad.
Il faut conserver la distinction dans toute télémétrie de réussite.
14. Wait optimisation LowID
Le code possède un cas où il évite d’initier un callback si le LowID est déjà dans notre upload queue et devrait revenir lui-même.
C’est une optimisation intéressante car elle combine :
UploadState
+
DownloadState
pour économiser du trafic.
C’est une preuve directe que les deux machines d’état doivent être vues ensemble.
15. DS_CONNECTING
Lorsque nous essayons activement de créer la relation nécessaire au download :
DS_CONNECTING
puis selon le chemin :
DS_WAITCALLBACK
DS_WAITCALLBACKKAD
peuvent apparaître.
16. Handshake
D12 :
Hello / HelloAnswer
permet d’obtenir :
UserHash
ClientID
ports
capabilities
server
Buddy
crypt options
À ce stade :
peer identity claimed
capabilities claimed
mais toutes ne sont pas encore observées avec succès.
17. Attachement à un client déjà connu
ClientList::AttachToAlreadyKnown() joue un rôle important lorsqu’un handshake entrant révèle qu’un objet correspond à un client déjà suivi.
Ce mécanisme évite les duplications d’identité mais gère aussi les conflits UserHash/SecureIdent.
D05/D03 doivent être connectés à D18 ici.
18. File Request
Après handshake, SendFileRequest() / multipacket moderne prépare les demandes liées à m_reqfile.
Échanges potentiels :
filename
File Status
hashset
AICH/FileIdentifier
source exchange
selon capabilities et données déjà connues.
19. DS_REQHASHSET
Lorsqu’un hashset manque :
DS_REQHASHSET
peut devenir un état intermédiaire.
Il ne s’agit ni d’une queue ni d’un transfert de payload.
20. File Not Found
Un FNF provoque une sémantique forte :
peer says it doesn't have requested file
D09 montre :
DontSwapTo(file)
→ A4AF éventuel
→ sinon suppression source
Cette cause doit être journalisée séparément de NNP.
21. Part Status
ProcessFileStatus() construit la carte des parties distantes.
Cette carte influence :
NNP
part selection
rarity
SX
A4AF
Mais D05/D06 la classent :
REMOTE_CLAIM
jusqu’à observation des données.
22. Demande de slot
Opcode historique :
OP_STARTUPLOADREQ 0x54
Le pair distant peut :
nous mettre dans sa queue
ou
nous accorder le slot
23. Queue state
Etat courant :
DS_ONQUEUE
Le client conserve notamment :
last asked time
remote queue rank
remote queue full flag
UDP reask information
24. Remote Queue Rank
Deux paquets historiques :
OP_QUEUERANK
OP_QUEUERANKING
avec tailles différentes.
D05 a montré :
SetRemoteQueueRank()
accepte essentiellement la valeur après validation de format/flood.
Elle doit rester une donnée non vérifiée.
25. Queue Rank Flood
CheckQueueRankFlood() protège notamment contre les QR non sollicités excessifs.
Le lifecycle trace doit pouvoir distinguer :
valid rank update
unsolicited
flood
26. Reask normal
D09 :
FILEREASKTIME ≈ 29 min
Le reask est une politique client historique.
Il ne faut pas le confondre avec :
SERVERREASKTIME
SX timers
Kad timers
DeadSource cooldown
27. Minimum fast ask
Opcodes.h :
MIN_REQUESTTIME = 10 min
BADCLIENTBAN = 4
Des demandes nettement trop fréquentes sont suivies comme comportement mauvais.
Toute optimisation de reask doit donc respecter aussi les protections du pair distant.
28. Reask UDP
Quand supporté, un reask léger peut utiliser :
OP_REASKFILEPING
puis réponse :
OP_REASKACK
OP_QUEUEFULL
OP_FILENOTFOUND
Cela évite une connexion TCP complète pour une simple maintenance de queue.
29. Callback reask
Un LowID peut aussi nécessiter des variantes :
OP_REASKCALLBACKUDP
OP_REASKCALLBACKTCP
Le cycle de queue dépend donc de la connectivité.
30. Slot accordé
Opcode :
OP_ACCEPTUPLOADREQ 0x55
La réception conduit au lancement du pipeline de téléchargement.
Le code doit nettoyer/initialiser :
pending blocks
download session stats
last data rate
compression state
selon contexte.
31. DS_DOWNLOADING
C’est l’état productif :
payload attendu/reçu
D06 couvre :
CreateBlockRequests
SendBlockRequests
GetNextRequestedBlock
32. Nombre de requêtes en vol
La branche 0.72a adapte le nombre de blocs demandés au débit source dans certains cas.
Exemples historiques observés D06 :
lent → 1 ou 2
normal → 3
rapide → 6 ou 9 pending localement
avec au maximum trois nouvelles plages dans un paquet historique de requête.
33. Requested blocks global file
Le CPartFile conserve aussi des réservations globales pour éviter que plusieurs sources reçoivent la même plage en régime normal.
C’est ce qui crée le cas Endgame :
unrequested = 0
file incomplete
34. Data receive
Pendant le transfert, nous obtenons enfin :
LOCAL_MEASURED
pour :
bytes
rate
timestamps
source of ranges
C’est un changement de niveau de confiance important.
35. Integrity transition
Après complétion de partie :
LOCAL_MEASURED
→ MD4/AICH
→ HASH_VERIFIED
ou :
CORRUPTED
D07 intervient alors.
36. Corruption peut modifier le lifecycle sans événement distant
Si AICH/ICH rouvre une zone :
GapList changes
Une source anciennement NNP peut redevenir utile.
D09 propose donc l’Event-Driven A4AF Reclassification comme piste interne.
37. Plus de nouveaux blocs
Dans SendBlockRequests() :
CreateBlockRequests()
→ pending empty
→ CancelTransfer
→ DS_NONEEDEDPARTS
→ SwapToAnotherFile()
Point crucial D09 :
pending empty
ne signifie pas toujours :
peer lacks useful part
mais parfois :
all useful gaps already reserved
39. OP_CANCELTRANSFER
Opcode :
0x56
Il termine une session de transfert active côté protocole.
Raison réelle à tracer :
NNP
swap
completion
session rotation
error
disconnect
40. OP_OUTOFPARTREQS
Opcode :
0x57
Sémantique :
plus de parties appropriées / plus de demandes possibles
Ne doit pas être automatiquement interprété comme hostilité ou DeadSource.
41. Déconnexion
Disconnected() est l’un des points centraux du lifecycle.
Sa réponse dépend de :
DownloadState
UploadState
raison socket
phase handshake
whether we were actively downloading
42. Déconnexion pendant download
Constat important :
DS_DOWNLOADING
→ disconnected
→ souvent DS_ONQUEUE
Le pair est conservé comme relation de source.
Cela empêche une coupure temporaire d’effacer tout l’historique.
43. Cas où le client peut être supprimé
Le retour de Disconnected() et les états autour déterminent si l’objet doit rester connu ou être supprimé.
Exemples plus graves :
connect failed
wrong file
invalid protocol
unrecoverable state
source no longer associated
44. DeadSourceList
La DeadSourceList n’est pas un ban.
Elle représente un cache temporaire d’échecs visant à éviter les tentatives répétées.
Deux portées :
global
local file
45. Timers DeadSource
Dans DeadSourceList.cpp, ordres de grandeur observés :
global HighID : 15 min
global LowID : 30 min
local file : 45 min
avec nettoyage périodique autour d’une heure.
Ces valeurs sont des politiques locales historiques.
46. Pourquoi deux portées
Une source peut être mauvaise pour :
un fichier précis
sans être globalement inutilisable.
Inversement, un échec de connectivité peut justifier un cooldown plus global.
Le double niveau permet d’éviter une suppression trop grossière.
47. DeadSource et rediscovery
Une source reçue à nouveau pendant son cooldown doit être reconnue comme déjà morte temporairement, sinon les canaux SERVER/KAD/SX pourraient contourner involontairement la protection.
D01, D08 et D18 doivent donc partager le même identifiant de source.
48. Upload queue parallèle
Le pair peut simultanément être présent dans notre :
UploadQueue
Son cycle :
STARTUPLOADREQ reçu
→ admission
→ US_ONUPLOADQUEUE
→ ranking
→ US_UPLOADING
→ rotation / cancel
49. Queue purge
MAX_PURGEQUEUETIME :
1 heure
Un pair qui ne renouvelle plus sa demande est retiré de notre upload waiting list.
Ce timer ne doit pas être mélangé avec le reask download.
50. Client tracking
Le code conserve aussi pendant un temps certaines informations sur des clients anciennement en upload queue :
KEEPTRACK_TIME ≈ 2h
TRACKED_CLEANUP_TIME ≈ 1h
Ces mécanismes servent notamment à éviter certains abus de réentrée/reset d’attente.
51. Connection timeouts
Opcodes.h contient notamment :
DOWNLOADTIMEOUT = 100 s
CONSERVTIMEOUT = 25 s
Ils ne sont pas des reask timers.
Leur rôle appartient à la détection d’une session/tentative devenue improductive.
52. D18-A — Le vrai objet à instrumenter est la transition
Un snapshot :
DS_ONQUEUE
est moins informatif que :
DS_CONNECTING
→ 1.2 s
→ DS_CONNECTED
→ 0.4 s
→ DS_ONQUEUE
La durée entre états est souvent la métrique réellement utile.
53. PeerLifecycleEvent
Proposition de modèle de trace local :
timestamp
peer_id_local
file_id_local
event
old_state
new_state
reason
connection_path
source_origin
remote_qr
bytes
rate
Pas besoin de changer le wire protocol.
54. Événements minimums
SOURCE_DISCOVERED
SOURCE_ADMITTED
SOURCE_DUPLICATE
CONNECT_ATTEMPT
CONNECT_PATH_SELECTED
CONNECTED
HELLO_COMPLETE
FILE_CONFIRMED
FILE_NOT_FOUND
PARTSTATUS_RECEIVED
HASHSET_REQUEST
QUEUE_ENTER
QUEUE_RANK
REASK
SLOT_ACCEPTED
DOWNLOAD_START
BLOCK_REQUESTED
FIRST_BYTE
PART_VERIFIED
PART_CORRUPTED
NNP
A4AF_SWAP
DISCONNECTED
DEADSOURCE_ADD
DEADSOURCE_EXPIRE
SOURCE_REMOVED
55. D18-B — First useful byte
Mesurer :
source discovered
→ first useful byte
décomposé en :
discovery delay
connect delay
queue delay
slot-to-first-byte
Cela permet de savoir où le temps est réellement perdu.
56. D18-C — Path success
Pour chaque type de connexion :
Direct TCP
Direct UDP Callback
Server Callback
Kad Callback
Wait-for-incoming
mesurer :
attempts
success
median latency
timeout
payload eventually received
57. D18-D — Reask effectiveness
Pour chaque reask :
old QR
new QR
queue full?
slot?
NNP?
file not found?
Mesurer le rendement du trafic de maintenance avant de discuter des timers.
58. D18-E — Disconnect reasons
Aujourd’hui, beaucoup d’analyses regroupent trop facilement :
source disconnected
Il faut distinguer :
remote close
local cancel
timeout
NNP transition
A4AF swap
file completion
protocol error
ban
connection reset
59. D18-F — DeadSource usefulness
Mesurer :
deadsource insertions
rediscovery during cooldown
retry after expiry
success after expiry
Question :
15/30/45 min évitent-ils réellement des tentatives inutiles en 2026 ?
60. D18-G — Upload/download cross-effect
Le cas historique :
LowID in our upload queue
→ wait for incoming instead of callback
mérite un compteur.
Mesurer combien de callbacks sont réellement évités.
61. D18-H — Source object lifetime
Un CUpDownClient peut survivre à plusieurs sockets.
Mesurer :
object creation
number of connect cycles
files associated
A4AF swaps
eventual destruction
Intéressant pour CPU/RAM et compréhension du cache de clients.
62. D18-I — Source count decomposition
Pour chaque fichier, produire expérimentalement :
TOTAL KNOWN
active downloading
on queue
connecting
NNP
A4AF
DeadSource
banned
error
Cela donnerait une image beaucoup plus honnête que le nombre brut de sources.
63. D18-J — Timeline debug
Exemple de trace :
12:00:00.000 SOURCE_DISCOVERED channel=SX provider=P
12:00:00.001 SOURCE_ADMITTED file=A
12:00:02.220 CONNECT_ATTEMPT path=DIRECT_TCP
12:00:02.510 CONNECTED
12:00:02.690 HELLO_COMPLETE
12:00:02.740 FILE_CONFIRMED
12:00:02.900 QUEUE_ENTER qr=248
12:29:12.100 REASK qr=173
12:58:21.021 REASK qr=51
13:27:30.412 SLOT_ACCEPTED
13:27:30.921 FIRST_BYTE
13:36:42.330 DISCONNECTED reason=REMOTE_CLOSE
13:36:42.331 STATE DS_DOWNLOADING→DS_ONQUEUE
Ce format rend immédiatement visibles les délais.
64. Trust dans la timeline
Associer aux événements leur niveau de preuve :
QR_RECEIVED → REMOTE_CLAIM
PARTSTATUS → REMOTE_CLAIM
CONNECTED → LOCAL_MEASURED
FIRST_BYTE → LOCAL_MEASURED
PART_OK → HASH_VERIFIED
SecureIdent → CRYPTO_VERIFIED
Cela unifie D05 avec le lifecycle.
65. Endgame dans la timeline
D06 doit fournir des événements :
ENDGAME_ENTER
LOCKED
RESERVATION_RELEASE
SPECULATIVE_RACE
RESCUE
D09 peut alors observer si une source passe artificiellement NNP à cause de réservations.
66. DiskGuard / causes locales
Une source ne doit pas être accusée d’être lente si le pipeline est bloqué localement par :
disk wait
hashing
decompression
write buffer
Le lifecycle trace doit pouvoir corréler réseau et pression disque.
67. Pas de nouveau protocole nécessaire
Tout le chantier D18 peut commencer avec :
logs
metrics
event IDs
sans modifier un seul paquet eD2K.
C’est l’une des raisons pour lesquelles ce dossier est une base de travail particulièrement sûre.
68. Fuzzing de machine d’état
Au-delà du parser, tester les séquences impossibles :
ACCEPTUPLOADREQ avant File Request
block data avant slot
QueueRank avant demande
double Hello
double Accept
Cancel après socket fermé
FileNotFound en plein download
L’objectif est :
pas de crash
pas d'état coincé
cleanup correct
73. Nettoyage des états fossiles
Candidats déjà identifiés :
DS_REMOTEQUEUEFULL
DS_TOOMANYCONNSKAD
Ne rien supprimer avant instrumentation.
Ajouter des compteurs :
state_enter_count
et mesurer sur semaines/mois.
75. Conclusion technique
D18 change le niveau d’analyse.
Au lieu d’étudier :
une fonction
nous étudions :
une trajectoire dans le temps
C’est probablement la bonne abstraction pour la phase suivante du projet.
La question devient :
quelle décision a fait passer ce pair
de l'état A à l'état B,
avec quelle preuve,
pour quel coût réseau,
et avec quel résultat utile ?
Une fois cette timeline mesurable, beaucoup de vieux paramètres pourront être étudiés objectivement.
76. Résumé des expérimentations prioritaires
L18-1 Connection Path Success
L18-2 Time to First Useful Byte
L18-3 Reask Effectiveness
L18-4 Disconnect Reason Taxonomy
L18-5 DeadSource Retry Outcome
L18-6 Upload/Download Cross-Effect
L18-7 Source-State Decomposition
L18-8 A4AF/NNP Event Correlation
L18-9 Endgame Lifecycle Correlation
L18-10 Disk/Network Causality
L18-11 State-machine fuzzing
L18-12 Legacy-state hit counters
Première phase :
100 % observation et tests.
Aucune modification fonctionnelle n’est nécessaire pour commencer.
Sources principales
- eMule Community 0.72a :
ClientStateDefs.hBaseClient.cppDownloadClient.cppUploadClient.cppDownloadQueue.cppUploadQueue.cppDeadSourceList.cppClientList.cppOpcodes.h
- Dossiers internes 01–17.
- Documentation protocolaire eDonkey/eMule déjà intégrée au corpus.
Statut : première version consolidée.
================================================================================================