5. Data Framing (Tramage des données)
Le protocole WebSocket utilise des trames pour transmettre des données. Ce chapitre définit le format et les règles de traitement des trames WebSocket.
5.1 Overview (Vue d'ensemble)
Une fois qu'une connexion WebSocket est établie, le client et le serveur peuvent transmettre des données de manière bidirectionnelle. Les données sont transmises sous forme d'une série de trames.
Concepts clés :
- Trame (Frame) : L'unité de transmission de base, contenant un en-tête et une charge utile
- Message : Données au niveau de l'application qui peuvent être composées d'une ou plusieurs trames
- Fragmentation : Les messages volumineux peuvent être envoyés en plusieurs trames
5.2 Base Framing Protocol (Protocole de tramage de base)
Structure de trame
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
Description des champs
FIN (1 bit)
0: Ce n'est pas la dernière trame du message (d'autres trames suivent)1: C'est la dernière trame du message (ou l'unique trame)
Exemple de fragmentation de message :
Trame 1: FIN=0, Opcode=0x1 (Text), Data="Hello "
Trame 2: FIN=0, Opcode=0x0 (Continuation), Data="World"
Trame 3: FIN=1, Opcode=0x0 (Continuation), Data="!"
Message complet : "Hello World!"
RSV1, RSV2, RSV3 (1 bit chacun)
- Réservés pour les extensions
- DOIT être 0 si aucune extension n'est négociée
- DOIT fermer la connexion si une valeur non nulle est reçue sans extension définie
Opcode (4 bits)
Définit le type de trame :
| Opcode | Type | Description |
|---|---|---|
0x0 | Continuation | Trame de continuation (trames suivantes d'un message fragmenté) |
0x1 | Text | Trame de texte (encodé UTF-8) |
0x2 | Binary | Trame binaire |
0x3-0x7 | - | Réservé (trames de données) |
0x8 | Close | Trame de fermeture |
0x9 | Ping | Trame Ping |
0xA | Pong | Trame Pong |
0xB-0xF | - | Réservé (trames de contrôle) |
MASK (1 bit)
- Client vers serveur : DOIT être 1
- Serveur vers client : DOIT être 0
Si MASK=1, Payload Data doit être masqué en utilisant Masking-key.
Payload Length (7 bits, 7+16 bits, ou 7+64 bits)
Encodage de la longueur de la charge utile :
- 0-125 : C'est la longueur réelle
- 126 : Les 16 bits suivants (2 octets) sont la longueur réelle (ordre des octets réseau)
- 127 : Les 64 bits suivants (8 octets) sont la longueur réelle (ordre des octets réseau)
Exemple :
Longueur de charge utile = 100 octets
→ Payload len = 100 (encodage direct)
Longueur de charge utile = 1000 octets
→ Payload len = 126
→ Extended payload length = 1000 (16 bits)
Longueur de charge utile = 100000 octets
→ Payload len = 127
→ Extended payload length = 100000 (64 bits)
Masking-key (0 ou 4 octets)
Si MASK=1, contient 32 bits (4 octets) de clé de masquage.
Payload Data (x+y octets)
Données de charge utile = Extension Data + Application Data
- Extension Data : Longueur x, déterminée par la négociation d'extension, par défaut 0
- Application Data : Longueur y, données d'application réelles
5.3 Client-to-Server Masking (Masquage client vers serveur)
Pourquoi le masquage est-il requis ?
Raison de sécurité : Prévenir les attaques par empoisonnement du cache. Certains proxys intermédiaires pourraient mettre en cache de manière incorrecte les trames WebSocket ; le masquage garantit l'imprévisibilité des données.
Algorithme de masquage
Les clients DOIVENT utiliser l'algorithme suivant pour masquer toutes les trames envoyées au serveur :
1. Générer une clé de masquage aléatoire de 32 bits
2. Placer la clé de masquage dans le champ Masking-key de l'en-tête de trame
3. Appliquer le masque à chaque octet de Payload Data :
transformed-octet-i = original-octet-i XOR masking-key[i MOD 4]
Implémentation de l'algorithme (JavaScript) :
function maskData(data, maskingKey) {
const masked = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
masked[i] = data[i] ^ maskingKey[i % 4];
}
return masked;
}
// Exemple
const data = Buffer.from('Hello');
const maskingKey = Buffer.from([0x37, 0xfa, 0x21, 0x3d]);
const masked = maskData(data, maskingKey);
// Décodage (utilisant le même algorithme)
const unmasked = maskData(masked, maskingKey); // 'Hello'
Points clés :
- XOR est auto-inverse :
(A XOR B) XOR B = A - Le serveur utilise le même algorithme pour décoder
- Doit utiliser une nouvelle clé aléatoire pour chaque trame envoyée
5.4 Fragmentation
Les messages volumineux peuvent être envoyés en plusieurs trames.
Règles de fragmentation
- Première trame : FIN=0, Opcode=type de données (0x1 ou 0x2)
- Trames intermédiaires : FIN=0, Opcode=0x0 (Continuation)
- Dernière trame : FIN=1, Opcode=0x0 (Continuation)
Exemple de fragmentation
Envoi du message "Hello World!" en trois trames :
Trame 1:
FIN = 0
Opcode = 0x1 (Text)
Payload = "Hello "
Trame 2:
FIN = 0
Opcode = 0x0 (Continuation)
Payload = "World"
Trame 3:
FIN = 1
Opcode = 0x0 (Continuation)
Payload = "!"
Contraintes de fragmentation
- Les trames de contrôle (Close, Ping, Pong) NE DOIVENT PAS être fragmentées
- Les trames de contrôle peuvent être insérées entre les trames de données fragmentées
- Les fragments doivent être envoyés et reçus dans l'ordre
5.5 Control Frames (Trames de contrôle)
Les trames de contrôle sont utilisées pour communiquer l'état de la connexion. Plage d'Opcode : 0x8-0xF.
5.5.1 Close (Trame de fermeture)
- Opcode :
0x8 - Peut inclure un code de fermeture et une raison
- Voir le chapitre 7 pour plus de détails
5.5.2 Ping (Trame Ping)
- Opcode :
0x9 - Objectif : Détection de pulsation, vérifier si la connexion est active
- Peut transporter des données d'application (max 125 octets)
- Le destinataire DOIT répondre avec une trame Pong
Client → Serveur: Ping (Opcode=0x9)
Serveur → Client: Pong (Opcode=0xA, même Payload)
5.5.3 Pong (Trame Pong)
- Opcode :
0xA - Objectif : Répondre à la trame Ping
- DOIT contenir le même Payload que la trame Ping
- Peut également être envoyé de manière proactive (pulsation non sollicitée)
Règles des trames de contrôle
- Longueur maximale de Payload : 125 octets
- NE DOIVENT PAS être fragmentées
- Peuvent être insérées entre les trames de données fragmentées
5.6 Data Frames (Trames de données)
Les trames de données transmettent des données d'application ou d'extension. Plage d'Opcode : 0x0-0x2, 0x3-0x7 réservés.
Text Frame (Trame de texte)
- Opcode :
0x1 - Le Payload DOIT être du texte encodé UTF-8 valide
- DOIT fermer la connexion si UTF-8 invalide reçu
Binary Frame (Trame binaire)
- Opcode :
0x2 - Le Payload peut être des données binaires arbitraires
- La couche application est responsable de l'interprétation
5.7 Examples (Exemples)
Trame de texte unique non masquée
0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (5 octets)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
Trame de texte unique masquée
0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
│ │ └─────┬────────┘ └──────┬───────────┘
│ │ │ └─ "Hello" masqué
│ │ └─ Clé de masquage
│ └─ MASK=1, Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
Message fragmenté
Trame 1: 0x01 0x03 0x48 0x65 0x6c // FIN=0, Text, "Hel"
Trame 2: 0x80 0x02 0x6c 0x6f // FIN=1, Continuation, "lo"
Message complet : "Hello"
Trame Ping
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (données optionnelles)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x9 (Ping)
5.8 Extensibility (Extensibilité)
Le protocole peut être étendu par :
- Opcode : 0x3-0x7 et 0xB-0xF réservés pour une utilisation future
- Bits RSV : Réservés pour une utilisation par les extensions
- Extension Data : Les extensions peuvent ajouter des données avant Payload
Les extensions doivent être négociées via la poignée de main (Sec-WebSocket-Extensions).
Liens de référence
- Chapitre précédent : 4. Opening Handshake (Poignée de main d'ouverture)
- Chapitre suivant : 6. Sending and Receiving Data (Envoi et réception de données)
- Explication détaillée : Détails de la structure de trame WebSocket