LYCÉE → PRÉPA · L22

Module L22 · Partie I · Robotique et systèmes embarqués

Faire parler les composants : bus, protocoles, réseaux, sécurité.

Un robot est un réseau : des capteurs sur I2C et SPI, des moteurs sur CAN, un microcontrôleur qui parle à un ordinateur par USB, des nœuds ROS sur Ethernet, une télémétrie en WiFi ou LoRa, un serveur dans le nuage. Chaque lien a son protocole, ses erreurs, ses latences et ses attaques possibles. Ce module descend du bit sur le fil jusqu’au chiffrement, en construisant les mécanismes essentiels : trames, sommes de contrôle, acquittements, files de messages, sockets, MQTT, TLS.

Durée : 3 séances · Prérequis : L07, L08, L18, séances 25-26. Objectifs : couches (physique, liaison, réseau, transport, application), UART/SPI/I2C/CAN et leurs compromis, conception d’un protocole binaire (trames, CRC, séquence, acquittement), sockets TCP/UDP, MQTT et publish/subscribe, ROS 2 sur DDS, latence et bande passante, cryptographie appliquée (hachage, HMAC, chiffrement, TLS, mise à jour signée).

Ce que vous saurez faire à la fin
  • Choisir le bon bus pour un capteur et expliquer pourquoi (vitesse, distance, nombre de fils, robustesse).
  • Concevoir et implémenter un protocole binaire robuste avec détection d’erreurs et resynchronisation.
  • Écrire un client/serveur TCP et UDP, et un échange MQTT.
  • Sécuriser un lien : authentifier, chiffrer, signer un firmware.

Références : Computer Networking: A Top-Down Approach (Kurose & Ross), spécifications I2C (NXP) et CAN (Bosch), documentation MQTT 5, Serious Cryptography (Aumasson).

Fiche de cours · Définitions

Communication, réseaux, cryptographie : définitions

Définition (modèle en couches). Physique (bits sur un support), liaison (trames entre voisins, adresses MAC, CRC), réseau (paquets IP routés de bout en bout), transport (TCP : fiable, ordonné, contrôle de flux et de congestion ; UDP : datagrammes sans garantie), application (HTTP, MQTT, DNS, SSH). Chaque couche encapsule la précédente avec un en-tête.
Définition (adressage, routage). Adresse IPv4 32 bits, masque /n (les n premiers bits identifient le réseau). Une machine envoie directement sur son réseau, sinon à sa passerelle ; les routeurs choisissent le prochain saut par table de routage (préfixe le plus long). NAT : partage d’une adresse publique. DNS : nom → adresse.
Définition (latence, débit, gigue). Latence : temps de traversée (propagation + transmission + files d’attente + traitement). Débit : bits par seconde. Gigue : variation de la latence. Produit délai × bande passante : quantité de données « en vol » nécessaire pour saturer un lien.
Définition (détection et correction d’erreurs). Un code ajoute de la redondance : bit de parité (détecte 1 erreur), CRC (division polynomiale, détecte les rafales), codes de Hamming (corrigent 1 erreur), Reed-Solomon, LDPC. La distance de Hamming minimale d d’un code permet de détecter d − 1 erreurs et d’en corriger ⌊(d − 1)/2⌋.
Définition (cryptographie). Chiffrement symétrique (AES : même clé pour chiffrer et déchiffrer, rapide) ; asymétrique (RSA, courbes elliptiques : clé publique pour chiffrer/vérifier, clé privée pour déchiffrer/signer) ; fonction de hachage (SHA-256 : résistante aux collisions et à la préimage) ; MAC/HMAC (authentification d’un message par clé partagée) ; signature ; échange de clés Diffie-Hellman ; TLS combine tout cela.
Définition (sécurité d’un système embarqué). Surface d’attaque : ports ouverts, mises à jour, mots de passe par défaut, bus non authentifiés (CAN). Principes : moindre privilège, défense en profondeur, mises à jour signées, secrets jamais en dur dans le code.

Fiche de cours · Formules

Formules à connaître

Latence = distance/vitesse + taille/débit + attente + traitement ; 1 km de fibre ≈ 5 µs ; 1 500 octets à 100 Mbit/s = 120 µs
Débit TCP ≤ fenêtre / RTT ; capacité de Shannon C = B·log₂(1 + S/N) bit/s
Distance de Hamming d : détecte d − 1 erreurs, corrige ⌊(d − 1)/2⌋ ; Hamming(7,4) : d = 3, 4 bits de données, 3 de parité
CRC : reste de la division polynomiale M(x)·xr par G(x) sur GF(2) (XOR) ; détecte toute rafale de longueur ≤ r et toute erreur simple si G a ≥ 2 termes
Probabilité qu’un paquet de n bits soit correct avec un taux d’erreur bit p : (1 − p)ⁿ ≈ e−np
RSA : n = pq, φ(n) = (p−1)(q−1), e·d ≡ 1 (mod φ(n)) ; chiffrer c = me mod n ; déchiffrer m = cd mod n
Diffie-Hellman : A = ga mod p, B = gb mod p ; secret partagé gab = Ba = Ab mod p
Exponentiation modulaire rapide : O(log e) multiplications modulaires (L09) — RSA 2048 bits ≈ 2 048 carrés
Paradoxe des anniversaires : collision de hachés de n bits probable après ≈ 2n/2 essais ⇒ 256 bits pour une sécurité de 128 bits
Petit théorème de Fermat : ap−1 ≡ 1 (mod p) si p premier et p ∤ a ; Euler : aφ(n) ≡ 1 (mod n) si pgcd(a, n) = 1

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire (1/2)

Théorème 1 (correction de RSA). Avec n = pq, ed ≡ 1 (mod (p−1)(q−1)), pour tout m ∈ [0, n[ : (me)d ≡ m (mod n).
ed = 1 + k(p−1)(q−1). Modulo p : si p ∤ m, Fermat donne mp−1 ≡ 1, donc med = m·(mp−1)k(q−1) ≡ m (mod p) ; si p | m, les deux côtés sont ≡ 0. Donc med ≡ m (mod p), et de même (mod q). Comme p et q sont premiers distincts, le théorème des restes chinois donne med ≡ m (mod pq). Sécurité : calculer d exige φ(n), donc factoriser n — infaisable pour n de 2 048 bits (record ≈ 829 bits). Fermat lui-même : dans le groupe multiplicatif (ℤ/pℤ)*, d’ordre p−1, l’ordre de a divise p−1 (Lagrange), d’où ap−1 = 1.
Théorème 2 (Diffie-Hellman : les deux parties obtiennent le même secret, l’espion non).
Alice calcule Ba = (gb)a = gab ; Bob calcule Ab = (ga)b = gab (commutativité des exposants). L’espion voit p, g, A = ga, B = gb ; retrouver a depuis A est le logarithme discret, sans algorithme efficace connu pour p de 2 048 bits (ou sur courbe elliptique de 256 bits). Limite : sans authentification, un attaquant au milieu peut faire deux échanges séparés (avec Alice et avec Bob) — d’où la signature des paramètres dans TLS.
Théorème 3 (un code de distance d corrige ⌊(d−1)/2⌋ erreurs).
Soit t = ⌊(d−1)/2⌋ et un mot reçu r à distance ≤ t d’un mot de code c. Pour tout autre mot de code c′, d(c, c′) ≥ d ≥ 2t + 1 ; par l’inégalité triangulaire, d(r, c′) ≥ d(c, c′) − d(r, c) ≥ 2t + 1 − t = t + 1 > t. Donc c est l’unique mot de code à distance ≤ t de r : le décodage « au plus proche » est correct. Détection : si ≤ d − 1 erreurs, le mot reçu n’est pas un mot de code (il faudrait d changements), donc l’erreur est détectée. Hamming(7,4) : d = 3, corrige 1 erreur avec 3 bits de redondance — chaque bit de parité couvre les positions dont l’indice binaire a un certain bit à 1, et le « syndrome » (3 bits) donne directement la position de l’erreur.

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire (2/2)

Théorème 4 (CRC : détection des rafales). Si G(x) est de degré r avec un terme constant non nul, le CRC détecte toute rafale d’erreurs de longueur ≤ r.
Le mot transmis T(x) est divisible par G(x) par construction (T = M·xr − reste). Une erreur E(x) est détectée ssi G ∤ E. Une rafale de longueur ≤ r s’écrit E(x) = xi·B(x) avec deg B ≤ r − 1 et B(0) = 1. G(x) ne divise pas xi (terme constant non nul, donc x ∤ G, et G irréductible… ou simplement pgcd(G, xi) = 1) et ne divise pas B (degré trop petit, B ≠ 0). Donc G ∤ E. Une erreur simple E = xi est détectée si G a au moins deux termes ; toute erreur double xi + xj = xi(1 + xj−i) l’est si G ne divise aucun 1 + xk pour k < longueur de trame — propriété des polynômes primitifs choisis pour CRC-16/CRC-32. Un CRC de r bits laisse passer une erreur aléatoire avec probabilité 2−r.
Théorème 5 (pourquoi hacher les mots de passe avec un sel et une fonction lente). Stocker H(mdp) sans sel permet une attaque par table précalculée sur tous les comptes à la fois ; un sel aléatoire par compte impose un calcul par compte ; une fonction lente (PBKDF2, bcrypt, Argon2 : ~100 ms) multiplie le coût de l’attaquant par 10⁶ par rapport à SHA-256.
Sans sel : l’attaquant calcule H(m) pour les 10⁹ mots de passe courants une fois (table), puis compare à tous les hachés volés : coût 10⁹ hachés, quel que soit le nombre de comptes. Avec sel si par compte : il doit calculer H(si ‖ m) pour chaque compte, coût 10⁹ × nombre de comptes. Avec une fonction à 100 ms au lieu de 100 ns : 10⁹ essais prennent 3 ans par compte au lieu de 100 s. Pour l’utilisateur légitime, 100 ms par connexion est imperceptible. Ne jamais chiffrer les mots de passe (la clé serait sur le serveur) ni les stocker en clair.
Théorème 6 (TCP : pourquoi la fenêtre limite le débit). Un émetteur qui attend l’acquittement de chaque fenêtre de W octets ne peut dépasser W/RTT.
Au plus W octets sont non acquittés ; un acquittement met au moins RTT à revenir ; donc au plus W octets par RTT. Pour saturer un lien de 1 Gbit/s avec RTT 100 ms, il faut W ≥ 12,5 Mo (produit délai × bande passante) : d’où l’option de mise à l’échelle de fenêtre. En cas de perte, TCP divise sa fenêtre (contrôle de congestion) : sur des liens radio avec pertes non dues à la congestion, TCP s’effondre — raison pour laquelle la télémétrie robotique utilise souvent UDP (ou QUIC/DDS avec QoS).

Fiche de cours · Méthodes

Méthodes et pièges

Méthode — concevoir un protocole série robuste. Trame = délimiteur | longueur | type | données | CRC-16. Parseur en machine à états (résynchronisation sur le délimiteur, échappement ou COBS). Numéro de séquence pour détecter les pertes ; acquittement seulement si nécessaire ; horodatage. Tester avec injection d’erreurs (bits inversés, octets perdus, trames tronquées) : le parseur ne doit jamais planter ni accepter une trame corrompue.
Méthode — sécuriser un robot connecté. Aucun mot de passe par défaut ; SSH par clés, pas de telnet ; mises à jour signées (vérifier la signature avant d’écrire la flash) ; TLS pour toute télémétrie sortante ; secrets dans un stockage dédié, jamais dans Git ; journaliser les connexions ; réseau du robot isolé (VLAN) ; principe du moindre privilège pour chaque service.
Méthode — diagnostiquer un réseau. ping (joignabilité, RTT), traceroute (chemin), ss -tulpn (ports ouverts), tcpdump/Wireshark (voir les paquets), iperf3 (débit). Mesurer avant d’optimiser : est-ce la latence, le débit, ou les pertes ?

Pièges : inventer sa propre cryptographie ; utiliser MD5/SHA-1 pour la sécurité ; réutiliser un nonce ou un IV ; comparer des MAC avec == (fuite temporelle : comparaison en temps constant) ; CAN sans authentification exposé à l’extérieur ; supposer qu’UDP livre dans l’ordre ; oublier l’endianness dans les trames binaires ; CRC calculé sur les mauvais octets (inclure la longueur et le type).

Fiche de cours · Exercices corrigés

Exercices corrigés

Exercice 1. RSA jouet : p = 11, q = 13, e = 7. Calculer n, φ(n), d, chiffrer m = 9, déchiffrer, et expliquer pourquoi ce RSA est cassable en une seconde.
Correction. n = 143, φ = 10·12 = 120. d = e⁻¹ mod 120 : Euclide étendu, 120 = 17·7 + 1 ⇒ 7·(−17) ≡ 1 ⇒ d = −17 mod 120 = 103. Vérification 7·103 = 721 = 6·120 + 1 ✓. Chiffrer : 9⁷ mod 143 : 9² = 81, 9⁴ = 81² = 6561 = 45·143 + 126 → 126, 9⁷ = 9⁴·9²·9 = 126·81·9 mod 143 : 126·81 = 10206 = 71·143 + 53 → 53 ; 53·9 = 477 = 3·143 + 48 → c = 48. Déchiffrer : 48¹⁰³ mod 143 par exponentiation rapide (7 carrés) redonne 9. Cassable : factoriser 143 = 11·13 est immédiat ; la sécurité vient uniquement de la taille de n (2 048 bits : 617 chiffres) — et il faut aussi un rembourrage aléatoire (OAEP), sans quoi me est déterministe et devinable pour de petits messages.
Exercice 2. Une liaison radio a un taux d’erreur bit de 10⁻⁴. Trames de 200 octets. Probabilité qu’une trame soit corrompue ? Avec un CRC-16, probabilité qu’une trame corrompue passe inaperçue ? Faut-il des trames plus courtes ?
Correction. n = 1 600 bits : P(corrompue) = 1 − (1 − 10⁻⁴)¹⁶⁰⁰ ≈ 1 − e−0,16 ≈ 14,8 %. CRC-16 : une erreur non détectée a une probabilité ≈ 2⁻¹⁶ = 1,5·10⁻⁵ conditionnellement à une corruption ; globalement ≈ 2,2·10⁻⁶ par trame — à 100 trames/s, une trame fausse acceptée toutes les 1,3 h : trop pour une commande moteur ; passer en CRC-32 (2,3·10⁻¹⁰, une par an et demi) ou ajouter un acquittement. Trames plus courtes : 50 octets → 3,9 % de pertes mais 4× plus d’en-têtes ; l’efficacité (données utiles × P(succès) / taille totale) est maximale autour de 1/√(p·en-tête) — ici ≈ 50–100 octets. Mieux : un code correcteur (Hamming ou Reed-Solomon) qui récupère les trames à une erreur.
Exercice 3. Un robot envoie sa position toutes les 20 ms à une station via Wi-Fi (RTT 30 ms, pertes 2 %). Faut-il TCP ou UDP ? Concevoir le message et le comportement en cas de perte.
Correction. UDP : une position perdue est remplacée par la suivante 20 ms plus tard ; TCP retransmettrait (≥ RTT = 30 ms de retard, puis livraison de positions périmées dans l’ordre, et réduction de fenêtre à chaque perte). Message : en-tête (magic, version), numéro de séquence (16 bits), horodatage (µs, 32 bits), x, y, θ (float32 ou int32 en mm/mrad), CRC-16 ; ~24 octets. Récepteur : ignorer les séquences plus anciennes que la dernière reçue (réordonnancement), extrapoler brièvement si aucune position depuis 60 ms, passer en sécurité (arrêt) après 200 ms sans nouvelle. Les commandes vers le robot, elles, exigent fiabilité et fraîcheur : UDP avec numéro de séquence et répétition périodique de la dernière commande (« état désiré » plutôt que « événement »), et arrêt automatique si le flux cesse.

01 / Bus embarqués

UART, SPI, I2C, CAN : quatre façons de mettre des bits sur un fil

BusFilsDébit typiqueDistanceTopologieUsage
UART (série)2 (TX, RX) + masse9,6 kb/s – 3 Mb/squelques m (RS-485 : 1 km)point à pointConsole, GPS, liaison PC ↔ carte
SPI4 (SCK, MOSI, MISO, CS par esclave)1 – 50 Mb/scmmaître / esclaves, un CS chacunÉcrans, cartes SD, IMU rapides, ADC
I2C2 (SDA, SCL) + pull-ups100 kb/s – 1 Mb/scm – 1 mmulti-esclaves adressés (7 bits)Capteurs (température, IMU, EEPROM)
CAN2 (différentiel)1 Mb/s (CAN FD : 5)40 m à 1 Mb/s, 1 km à 50 kb/smulti-maître, arbitrage par prioritéAutomobile, moteurs de robots, industriel
USB / Ethernet4 / 812 Mb/s – 10 Gb/s5 m / 100 mhôte-périphérique / commutéPC, caméras, ROS entre machines
Les compromis

UART : simple, sans horloge partagée (asynchrone), deux nœuds seulement. SPI : très rapide, horloge explicite, mais un fil de sélection par esclave. I2C : deux fils pour 100 capteurs, mais lent, sensible aux longueurs et aux « bus bloqués » (un esclave qui tient SDA bas : prévoir une routine de récupération par 9 coups d’horloge). CAN : robuste (différentiel, CRC 15 bits, acquittement matériel, arbitrage sans collision : la trame d’identifiant le plus bas gagne), c’est pourquoi une voiture en a trois ou quatre. Au-dessus : les protocoles applicatifs (CANopen, DroneCAN, Modbus sur RS-485).

01 / Bus embarqués

I2C et SPI vus du code : lire une IMU

// I2C sur ESP32 (Arduino) : MPU-6050 à l'adresse 0x68. Registre 0x3B = accéléro X (16 bits, big-endian)
#include <Wire.h>
void setup() {
  Wire.begin(21, 22, 400000);         // SDA, SCL, 400 kHz
  Wire.beginTransmission(0x68);
  Wire.write(0x6B); Wire.write(0);    // PWR_MGMT_1 = 0 : réveiller
  Wire.endTransmission();
}
void lire_accel(int16_t *ax, int16_t *ay, int16_t *az) {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B);                   // pointer sur le premier registre
  Wire.endTransmission(false);        // START répété : garder le bus
  Wire.requestFrom(0x68, 6);          // 6 octets : X, Y, Z
  *ax = (Wire.read() << 8) | Wire.read();
  *ay = (Wire.read() << 8) | Wire.read();
  *az = (Wire.read() << 8) | Wire.read();
}
// Conversion : ±2 g pleine échelle sur 16 bits → 16384 LSB/g
// SPI (Pico, C SDK) : même capteur en SPI, 10× plus vite. Bit 7 de l'adresse = lecture.
spi_init(spi0, 10 * 1000 * 1000);
gpio_put(CS, 0);                              // sélectionner
uint8_t tx[7] = { 0x3B | 0x80 };              // lecture à partir de 0x3B, 6 octets suivent
uint8_t rx[7];
spi_write_read_blocking(spi0, tx, rx, 7);     // full-duplex : on envoie et reçoit en même temps
gpio_put(CS, 1);
int16_t ax = (rx[1] << 8) | rx[2];

Trois pièges universels : l’endianness (quel octet en premier ?), le signe (complément à deux), et l’échelle (LSB par unité physique, dans la fiche technique). struct en Python et les casts en C rendent tout cela explicite.

02 / Protocoles

Concevoir un protocole binaire : trame, longueur, CRC, séquence

Les ingrédients d’un bon protocole
  • Synchronisation : un motif de début (et/ou un encodage comme COBS qui interdit un octet réservé dans les données) pour retrouver le début d’une trame après une erreur.
  • Longueur explicite : savoir où finit la trame sans la parser.
  • Détection d’erreurs : somme de contrôle (faible), CRC (détecte toutes les erreurs de 1, 2 bits et les rafales < 16 bits pour un CRC-16), ou code correcteur (Hamming, Reed-Solomon) si retransmettre est impossible (espace, diffusion).
  • Numéro de séquence : détecter pertes et doublons ; acquittement et retransmission si la fiabilité compte (mais pas pour une télémétrie à 100 Hz : une mesure perdue est remplacée par la suivante).
  • Version du protocole dans l’en-tête : la compatibilité future.

MAVLink (drones), Protobuf/nanopb (Google, sérialisation compacte avec schéma), CBOR : autant de choix éprouvés avant d’inventer le sien.

02 / Protocoles

Fiabilité sur canal avec pertes : acquittements, fenêtre, temporisateurs (l’idée de TCP)

TCP fait exactement cela : numéros de séquence, acquittements cumulatifs, fenêtre glissante, temporisateur estimé à partir du RTT mesuré (Jacobson), et en plus un contrôle de congestion (réduire la fenêtre quand le réseau sature). UDP ne fait rien de tout cela : c’est à l’application de décider si elle veut de la fiabilité (ROS 2 sur DDS choisit par topic : « fiable » pour les commandes, « best effort » pour les images).

03 / Réseaux

Les couches, et ce qu’un paquet traverse

CoucheUnitéAdresseExemplesSur le robot
ApplicationmessageURL, topicHTTP, MQTT, DDS, SSHAPI du robot, télémétrie, ROS 2
Transportsegmentport (0-65535)TCP (fiable, ordonné), UDP (rapide, sans garantie)TCP : commandes ; UDP : vidéo, capteurs
RéseaupaquetIP (192.168.1.42, 2001:db8::1)IP, ICMP (ping), routageRobot et PC sur le même sous-réseau
LiaisontrameMAC (aa:bb:cc:dd:ee:ff)Ethernet, WiFi (802.11), BluetoothPoint d’accès du robot
PhysiquebitsCuivre, fibre, radio2,4 GHz : encombré ; 5 GHz : portée moindre
$ ping robot.local                 # ICMP : le robot répond-il ? latence aller-retour (RTT)
$ ip addr / ifconfig               # mes adresses
$ ss -tulpn / netstat -an          # qui écoute sur quels ports
$ nc -l 5000                       # écouter en TCP sur 5000 (netcat) ;  nc robot.local 5000 pour se connecter
$ tcpdump -i wlan0 port 1883       # voir passer les paquets MQTT (Wireshark en graphique)
$ iperf3 -s / iperf3 -c robot      # mesurer la bande passante réelle du WiFi
$ mtr 8.8.8.8                      # la route et la latence de chaque saut

Ordres de grandeur : RTT sur un câble Ethernet local ≈ 0,2 ms ; WiFi ≈ 2-20 ms avec des pics à 200 ms ; 4G ≈ 50 ms ; à travers l’Atlantique ≈ 80 ms (la lumière ne va pas plus vite). Un contrôle de robot à 100 Hz ne se fait jamais à travers le WiFi : la boucle rapide reste sur le microcontrôleur (module L18), le réseau ne transporte que des objectifs.

03 / Réseaux

Sockets : TCP et UDP en Python (à exécuter sur PC), et une simulation dans la page

# serveur TCP : reçoit des commandes ligne par ligne
import socket
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", 5000)); srv.listen()
while True:
    conn, addr = srv.accept()                 # bloque jusqu'à un client
    with conn:
        tampon = b""
        while chunk := conn.recv(1024):        # TCP est un FLUX : recv peut rendre une demi-ligne ou trois
            tampon += chunk
            while b"\n" in tampon:
                ligne, tampon = tampon.split(b"\n", 1)
                conn.sendall(b"OK " + ligne + b"\n")

# client
c = socket.create_connection(("robot.local", 5000), timeout=2)
c.sendall(b"avancer 0.3\n"); print(c.recv(1024))
# UDP : télémétrie à 50 Hz, un datagramme par mesure, sans connexion
import socket, struct, time
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
    s.sendto(struct.pack("<Ifff", int(time.time() * 1000), x, y, th), ("192.168.1.10", 6000))
    time.sleep(0.02)
# récepteur
r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); r.bind(("0.0.0.0", 6000)); r.settimeout(0.5)
try:
    data, addr = r.recvfrom(64); t, x, y, th = struct.unpack("<Ifff", data)
except socket.timeout:
    print("robot silencieux depuis 0,5 s")

03 / Réseaux

MQTT : le publish/subscribe de l’Internet des objets

# pip install paho-mqtt ; un broker : mosquitto (local) ou test.mosquitto.org (public, pour essayer)
import paho.mqtt.client as mqtt, json

def sur_message(client, userdata, msg):
    print(msg.topic, json.loads(msg.payload))

c = mqtt.Client(); c.on_message = sur_message
c.connect("localhost", 1883)
c.subscribe("robots/+/telemetrie")          # + : joker d'un niveau ; # : tout ce qui suit
c.loop_start()
c.publish("robots/r2/commande", json.dumps({"action": "avancer", "v": 0.3}), qos=1)
// ESP32 (PubSubClient) : publie la batterie toutes les 10 s, s'abonne aux commandes
client.setServer("192.168.1.10", 1883);
client.setCallback([](char *topic, byte *p, unsigned len) { /* parser JSON, agir */ });
client.connect("r2", "user", "motdepasse", "robots/r2/statut", 1, true, "hors-ligne");   // testament : publié si r2 disparaît
client.subscribe("robots/r2/commande");
client.publish("robots/r2/statut", "en-ligne", true);      // retenu : un nouvel abonné le reçoit immédiatement

MQTT = le patron Observateur (L02) à l’échelle d’un réseau, avec un intermédiaire (broker) qui découple émetteurs et récepteurs. QoS 0/1/2 (au plus une fois / au moins / exactement), messages retenus, testament : tout ce qu’il faut pour une flotte de robots ou de capteurs. ROS 2 utilise DDS, plus riche (typé, QoS fines, découverte automatique) mais plus lourd.

04 / Sécurité

Cryptographie appliquée : ce que garantit chaque brique

La pile de sécurité d’un robot connecté
  • Transport chiffré et authentifié : TLS (HTTPS, MQTTS, SSH) avec certificats ; sur microcontrôleur, mbedTLS/wolfSSL ; sur lien radio léger, une clé pré-partagée et AES-GCM (ChaCha20-Poly1305 sans accélération matérielle).
  • Authentification des commandes : HMAC ou signature ; anti-rejeu par compteur.
  • Mise à jour signée : le firmware est signé (Ed25519) par le développeur ; le bootloader vérifie la signature avant de flasher. Sans cela, quiconque accède au réseau peut installer son propre code.
  • Secure boot et clés dans un élément sécurisé : la clé ne quitte jamais la puce.
  • Réseau : pas de robot exposé sur Internet ; VPN (WireGuard) ; pare-feu ; mots de passe par défaut changés (Mirai, 2016 : 600 000 caméras enrôlées parce que « admin/admin »).
  • Règle d’or : n’inventez pas votre cryptographie. Utilisez des bibliothèques auditées (libsodium, cryptography en Python) et des protocoles standard.

Cours

Cours 1 — Signal, bruit, débit : ce que la physique impose à toute communication

NotionDéfinitionConséquence pratique
Bande passante B (Hz)Plage de fréquences que le canal laisse passerUn câble long ou une radio étroite limitent le débit
Rapport signal/bruit S/NPuissance du signal / puissance du bruit (souvent en dB : 10 log₁₀)Distance, blindage, puissance d’émission le déterminent
Capacité de ShannonC = B log₂(1 + S/N) bits/sLimite absolue ; les codes correcteurs modernes (LDPC) s’en approchent à 1 dB
Taux d’erreur binaire (BER)Fraction de bits faux10⁻⁶ sur UART propre, 10⁻² en radio dégradée : d’où CRC et retransmissions
LatenceTemps entre émission et réceptionPropagation (5 µs/km sur cuivre, 3,3 µs/km en radio) + sérialisation (taille/débit) + traitement + files d’attente
GigueVariation de la latencePlus gênante que la latence pour le contrôle ; tampon de gigue en audio/vidéo
DifférentielSignal transmis comme différence entre deux filsImmunité au bruit de mode commun : RS-485, CAN, USB, Ethernet vont loin ; UART simple non

Cours

Cours 2 — Exemple travaillé : le CRC, pourquoi il détecte ce qu’il détecte

Principe. Le message est vu comme un polynôme M(x) sur F₂ (coefficients 0/1, + = XOR). On choisit un polynôme générateur G(x) de degré r (CRC-16-CCITT : x¹⁶ + x¹² + x⁵ + 1 = 0x1021). Le CRC est le reste R(x) de M(x)·xr divisé par G(x) ; on transmet M·xr + R, qui est divisible par G. Le récepteur divise : reste nul ⇔ pas d’erreur détectée.

Une erreur E(x) est détectée ssi G ne divise pas E. D’où les propriétés : (1) toute erreur d’1 bit (E = xk) est détectée car G a au moins deux termes ; (2) toute erreur de 2 bits (E = xk(xj + 1)) est détectée si G ne divise aucun xj + 1 pour j < longueur — vrai pour un G bien choisi (période de G grande) ; (3) tout nombre impair d’erreurs est détecté si (x + 1) divise G ; (4) toute rafale de longueur ≤ r est détectée (E = xk·B(x) avec deg B < r : G ne peut pas diviser B). Une rafale plus longue échappe avec probabilité 2−r.

Cours

Cours 3 — Cryptographie : les primitives, leurs garanties, leurs usages corrects

PrimitiveGarantitNe garantit pasStandard à utiliserErreur classique
HachageIntégrité (empreinte), résistance aux collisionsAuthenticité (n’importe qui peut hacher)SHA-256, SHA-3, BLAKE2MD5/SHA-1 (cassés) ; hacher un mot de passe sans sel ni lenteur
Hachage de mot de passeLenteur contre la force brute, sel contre les tablesArgon2id, bcrypt, scryptUtiliser un hachage rapide
MACAuthenticité + intégrité avec clé partagéeConfidentialité, non-répudiationHMAC-SHA256, Poly1305hash(clé ‖ message) (attaque par extension de longueur)
Chiffrement symétriqueConfidentialitéIntégrité (sauf mode AEAD)AES-GCM, ChaCha20-Poly1305 (AEAD : chiffre et authentifie)AES-ECB (motifs visibles) ; réutiliser un nonce
Échange de clésUne clé partagée sur canal publicL’identité de l’interlocuteurX25519 (ECDH)Sans authentification : homme du milieu
SignatureAuthenticité + non-répudiation avec clé publiqueConfidentialitéEd25519, ECDSA P-256, RSA-PSSSigner un hachage rapide de mot de passe… (rien à voir) ; mauvais aléa (PlayStation 3)
CertificatLie une clé publique à une identité via une autoritéX.509, TLS 1.3Désactiver la vérification du certificat « pour tester »
AléaImprévisibilitéos.urandom, secrets, TRNG matérielrandom (Mersenne Twister, prévisible) pour une clé

TP guidé

TP — Un lien robot ↔ PC complet : bus, protocole, réseau, sécurité (carte + PC, 4 h)

Exercices

Exercices auto-corrigés — bits et trames

Exercice 1 — COBS : encoder sans octet zéro pour délimiter les trames

Le codage COBS (Consistent Overhead Byte Stuffing) transforme n’importe quelle suite d’octets en une suite sans octet 0x00 (surcoût ≤ 1 octet par 254), ce qui permet d’utiliser 0x00 comme délimiteur de trame sans ambiguïté. Implémentez cobs_encoder et cobs_decoder ; vérifiez par propriété sur 500 messages aléatoires (dont des zéros consécutifs et des messages vides).

Correction
def cobs_encoder(data):
    out = bytearray(); bloc = bytearray()
    def flush(): out.append(len(bloc) + 1); out.extend(bloc); bloc.clear()
    for b in data:
        if b == 0: flush()
        else:
            bloc.append(b)
            if len(bloc) == 254: flush()
    flush(); return bytes(out)
def cobs_decoder(data):
    out = bytearray(); i = 0
    while i < len(data):
        code = data[i]; out.extend(data[i + 1:i + code]); i += code
        if code < 255 and i < len(data): out.append(0)
    return bytes(out)

Exercice 2 — Décodeur robuste par propriétés

Sur l’encodeur/décodeur de trames du cours (encoder, Decodeur), écrivez test_proprietes(n) : pour n messages aléatoires concaténés avec des parasites aléatoires entre eux et 1 % d’octets corrompus, toutes les trames non corrompues doivent être récupérées intactes et aucune trame invalide acceptée (vérifiez que chaque charge utile décodée est bien l’une des charges émises). Renvoyez (récupérées, rejetées).

Correction
def test_proprietes(n=300, p_corruption=0.01):
    charges = [bytes(random.randrange(256) for _ in range(random.randint(0, 40))) for _ in range(n)]
    flux = bytearray()
    for i, c in enumerate(charges):
        flux += encoder(i, 1, c)
        if random.random() < 0.3: flux += bytes(random.randrange(256) for _ in range(random.randint(1, 6)))
    for i in range(len(flux)):
        if random.random() < p_corruption: flux[i] ^= random.randrange(1, 256)
    d = Decodeur()
    for b in flux: d.alimenter(b)
    emises = set(charges)
    for seq, t, charge in d.trames: assert charge in emises, "trame acceptée jamais émise"
    return len(d.trames), d.erreurs

Avec un CRC-16, une fausse acceptation a une probabilité ≈ 2⁻¹⁶ par trame corrompue : sur 300 trames, on n’en verra pas ; sur un million de trames par jour, si. C’est pourquoi les protocoles critiques utilisent un CRC-32 et une longueur bornée.

Exercices

Exercices auto-corrigés — réseau et cryptographie

Exercice 3 — Estimateur de RTT et temporisateur adaptatif (Jacobson)

TCP estime le RTT par moyennes mobiles exponentielles : SRTT = (1−α)·SRTT + α·R et RTTVAR = (1−β)·RTTVAR + β·|SRTT − R| (α = 1/8, β = 1/4), et fixe le timeout RTO = SRTT + 4·RTTVAR. Implémentez Estimateur avec mesure(r) et rto() ; sur une série de RTT gaussiens (50 ± 10 ms) puis un saut à 150 ms, vérifiez que RTO reste au-dessus de 97 % des RTT réels et qu’il s’adapte au saut en moins de 20 mesures.

Correction
class Estimateur:
    def __init__(self): self.srtt = None; self.var = 0.0
    def mesure(self, r):
        if self.srtt is None: self.srtt, self.var = r, r / 2
        else: self.var = 0.75 * self.var + 0.25 * abs(self.srtt - r); self.srtt = 0.875 * self.srtt + 0.125 * r
    def rto(self): return self.srtt + 4 * self.var

Exercice 4 — Diffie-Hellman et l’attaque de l’homme du milieu, simulés

Avec p premier (fourni, 61 bits) et g = 2 : dh_cle_publique(secret), dh_secret_partage(mon_secret, sa_cle_publique). Vérifiez qu’Alice et Bob obtiennent la même clé. Puis simulez Mallory qui intercepte les deux clés publiques et les remplace par les siennes : montrez qu’Alice et Bob croient partager une clé alors que chacun la partage avec Mallory. Enfin, signer/verifier (HMAC avec une clé pré-partagée, en guise de signature) sur les clés publiques : l’attaque est détectée.

Correction
def dh_cle_publique(secret): return pow(g, secret, p)
def dh_secret_partage(mon_secret, sa_cle_publique): return pow(sa_cle_publique, mon_secret, p)
def signer(cle_auth, cle_publique): return hmac.new(cle_auth, str(cle_publique).encode(), hashlib.sha256).digest()
def verifier(cle_auth, cle_publique, tag): return hmac.compare_digest(signer(cle_auth, cle_publique), tag)

TLS remplace le HMAC à clé pré-partagée par une signature (clé privée du serveur) dont la clé publique est garantie par un certificat : c’est ainsi qu’on authentifie quelqu’un qu’on n’a jamais rencontré. Un p de 61 bits se casse en secondes (logarithme discret) : en pratique 2048 bits ou des courbes elliptiques 256 bits.

05 / Défis

Défi ★ — Un protocole complet PC ↔ carte

Consigne (PC + carte)

1) Implémentez en C sur la carte le décodeur de trames (sync, longueur, CRC-16 par table) dans un tampon circulaire (L18), et l’encodeur en Python côté PC. 2) Deux types de messages : commande (v, ω, séquence) PC → carte, télémétrie (t, x, y, θ, batterie) carte → PC à 50 Hz. 3) Mesurez : taux de trames rejetées en débranchant/rebranchant le câble, latence aller-retour d’un message « ping » (chrono PC). 4) Ajoutez un watchdog de communication : si aucune commande valide depuis 300 ms, les moteurs s’arrêtent.

Piste (CRC par table en C)
static uint16_t table[256];
void crc_init(void) { for (int i = 0; i < 256; i++) { uint16_t c = i << 8; for (int b = 0; b < 8; b++) c = (c & 0x8000) ? (c << 1) ^ 0x1021 : c << 1; table[i] = c; } }
uint16_t crc16(const uint8_t *d, size_t n) { uint16_t c = 0xFFFF; while (n--) c = (c << 8) ^ table[(c >> 8) ^ *d++]; return c; }

05 / Défis

Défi ★★ — Flotte de robots par MQTT et tableau de bord

Consigne (PC, plusieurs machines si possible)

1) Broker mosquitto local ; 3 « robots » simulés (scripts Python, ou ESP32 réels) publient position et batterie sur robots/<id>/telemetrie toutes les 100 ms, avec testament. 2) Un superviseur s’abonne à robots/+/telemetrie, détecte les robots muets (testament ou timeout), et publie des ordres sur robots/<id>/commande. 3) Tableau de bord web (L08) qui affiche la flotte en temps réel via MQTT-over-WebSocket. 4) Mesurez la latence de bout en bout (horodatage à l’émission) selon QoS 0/1/2 et le nombre de robots ; à partir de combien de messages/s le broker sature-t-il ?

Piste

Latence : le timestamp doit venir d’une horloge commune (NTP sur toutes les machines, ou aller-retour mesuré). QoS 2 coûte 4 paquets par message. Mosquitto tient des dizaines de milliers de messages/s sur un portable ; le goulot sera plutôt votre client Python et le WiFi.

05 / Défis

Défi ★★★ — Esprit prépa : codes correcteurs et échange de clés

Consigne

1) Implémentez le code de Hamming(7,4) : 4 bits de données, 3 de parité ; montrez qu’il corrige toute erreur de 1 bit et détecte celles de 2 bits ; calculez la distance minimale et prouvez ces propriétés. Généralisez à Hamming(15,11) avec des matrices sur F₂ (module L10, arithmétique modulo 2). 2) Simulez un canal binaire symétrique avec p = 1 % et comparez le taux d’erreur résiduel avec et sans code, pour 10⁶ bits. 3) Implémentez l’échange de clés de Diffie-Hellman sur un groupe multiplicatif modulo un premier p de 2048 bits (générez p avec Miller-Rabin, module L11 ; exponentiation rapide, module L04) et montrez qu’un espion qui voit tout passer ne peut pas calculer la clé sans résoudre un logarithme discret. 4) Expliquez pourquoi il faut quand même authentifier l’échange (attaque de l’homme du milieu) et comment TLS le fait.

Piste (Hamming(7,4))
G = np.array([[1,0,0,0,1,1,0],[0,1,0,0,1,0,1],[0,0,1,0,0,1,1],[0,0,0,1,1,1,1]])     # génératrice (systématique)
Hm = np.array([[1,1,0,1,1,0,0],[1,0,1,1,0,1,0],[0,1,1,1,0,0,1]])                  # de contrôle : H Gᵀ = 0 mod 2
def encoder(d): return d @ G % 2
def decoder(r):
    s = Hm @ r % 2                                            # syndrome : colonne de H où l'erreur est
    if s.any():
        col = [tuple(Hm[:, j]) for j in range(7)].index(tuple(s)); r = r.copy(); r[col] ^= 1
    return r[:4]
d = np.array([1, 0, 1, 1]); c = encoder(d); c[5] ^= 1; print(decoder(c), d)

Distance minimale 3 ⇒ corrige ⌊(3−1)/2⌋ = 1 erreur. Reed-Solomon (CD, QR codes, sondes spatiales) et LDPC/polaires (5G) reposent sur la même algèbre, sur des corps plus grands. Diffie-Hellman : g^a mod p et g^b mod p sont publics, g^ab est secret ; sa sécurité repose sur la difficulté conjecturée du logarithme discret — et un ordinateur quantique (module L24) la casserait, d’où la cryptographie post-quantique (Kyber) en cours de déploiement.

06 / Vérification

En TCP, le serveur reçoit les données par recv(1024). Peut-il supposer qu’un appel renvoie exactement un message envoyé par sendall ?

Deux questions supplémentaires

1. Pourquoi un CRC plutôt qu’une simple somme ? La somme rate des erreurs fréquentes (deux bits inversés qui se compensent, octets permutés) ; le CRC détecte toutes les rafales courtes.

2. Que protège un HMAC que le chiffrement seul ne protège pas ? L’authenticité : un message chiffré mais non authentifié peut être modifié (attaques par malléabilité). D’où AES-GCM, qui fait les deux.

Référence

Les mots à retenir

MotDéfinition
UART / SPI / I2C / CANBus série : asynchrone 2 fils / rapide 4 fils / adressé 2 fils / différentiel robuste multi-maître.
EndiannessOrdre des octets d’un nombre.
TrameUnité de transmission : sync, longueur, charge, CRC.
CRCDétection d’erreurs par division polynomiale.
Séquence / acquittement / fenêtreMécanismes de fiabilité (TCP).
TCP / UDPFlux fiable ordonné / datagrammes sans garantie.
SocketInterface de programmation réseau.
MQTT / DDSPublish/subscribe par broker / distribué (ROS 2).
Hachage / HMAC / signatureIntégrité / authentification par clé partagée / par clé publique.
Nonce / rejeuValeur unique par message / attaque par renvoi.
TLSTransport chiffré et authentifié par certificats.
Code correcteurRedondance qui corrige les erreurs (Hamming, Reed-Solomon).

Pour continuer

Fin de la partie I : votre robot est un système complet

Partie J : vers la recherche. Module suivant : l’informatique théorique — calculabilité, machines de Turing, complexité P/NP, automates et langages : ce que les ordinateurs ne peuvent pas faire, et pourquoi.

À faire chez soi

← L21SommaireL23 : Informatique théorique →