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
Fiche de cours · Formules
Formules à connaître
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (1/2)
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (2/2)
Fiche de cours · Méthodes
Méthodes et pièges
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
01 / Bus embarqués
UART, SPI, I2C, CAN : quatre façons de mettre des bits sur un fil
| Bus | Fils | Débit typique | Distance | Topologie | Usage |
|---|---|---|---|---|---|
| UART (série) | 2 (TX, RX) + masse | 9,6 kb/s – 3 Mb/s | quelques m (RS-485 : 1 km) | point à point | Console, GPS, liaison PC ↔ carte |
| SPI | 4 (SCK, MOSI, MISO, CS par esclave) | 1 – 50 Mb/s | cm | maître / esclaves, un CS chacun | Écrans, cartes SD, IMU rapides, ADC |
| I2C | 2 (SDA, SCL) + pull-ups | 100 kb/s – 1 Mb/s | cm – 1 m | multi-esclaves adressés (7 bits) | Capteurs (température, IMU, EEPROM) |
| CAN | 2 (différentiel) | 1 Mb/s (CAN FD : 5) | 40 m à 1 Mb/s, 1 km à 50 kb/s | multi-maître, arbitrage par priorité | Automobile, moteurs de robots, industriel |
| USB / Ethernet | 4 / 8 | 12 Mb/s – 10 Gb/s | 5 m / 100 m | hô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
| Couche | Unité | Adresse | Exemples | Sur le robot |
|---|---|---|---|---|
| Application | message | URL, topic | HTTP, MQTT, DDS, SSH | API du robot, télémétrie, ROS 2 |
| Transport | segment | port (0-65535) | TCP (fiable, ordonné), UDP (rapide, sans garantie) | TCP : commandes ; UDP : vidéo, capteurs |
| Réseau | paquet | IP (192.168.1.42, 2001:db8::1) | IP, ICMP (ping), routage | Robot et PC sur le même sous-réseau |
| Liaison | trame | MAC (aa:bb:cc:dd:ee:ff) | Ethernet, WiFi (802.11), Bluetooth | Point d’accès du robot |
| Physique | bits | — | Cuivre, fibre, radio | 2,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 sautOrdres 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édiatementMQTT = 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,
cryptographyen Python) et des protocoles standard.
Cours
Cours 1 — Signal, bruit, débit : ce que la physique impose à toute communication
| Notion | Définition | Conséquence pratique |
|---|---|---|
| Bande passante B (Hz) | Plage de fréquences que le canal laisse passer | Un câble long ou une radio étroite limitent le débit |
| Rapport signal/bruit S/N | Puissance du signal / puissance du bruit (souvent en dB : 10 log₁₀) | Distance, blindage, puissance d’émission le déterminent |
| Capacité de Shannon | C = B log₂(1 + S/N) bits/s | Limite absolue ; les codes correcteurs modernes (LDPC) s’en approchent à 1 dB |
| Taux d’erreur binaire (BER) | Fraction de bits faux | 10⁻⁶ sur UART propre, 10⁻² en radio dégradée : d’où CRC et retransmissions |
| Latence | Temps entre émission et réception | Propagation (5 µs/km sur cuivre, 3,3 µs/km en radio) + sérialisation (taille/débit) + traitement + files d’attente |
| Gigue | Variation de la latence | Plus gênante que la latence pour le contrôle ; tampon de gigue en audio/vidéo |
| Différentiel | Signal transmis comme différence entre deux fils | Immunité 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
| Primitive | Garantit | Ne garantit pas | Standard à utiliser | Erreur classique |
|---|---|---|---|---|
| Hachage | Intégrité (empreinte), résistance aux collisions | Authenticité (n’importe qui peut hacher) | SHA-256, SHA-3, BLAKE2 | MD5/SHA-1 (cassés) ; hacher un mot de passe sans sel ni lenteur |
| Hachage de mot de passe | Lenteur contre la force brute, sel contre les tables | — | Argon2id, bcrypt, scrypt | Utiliser un hachage rapide |
| MAC | Authenticité + intégrité avec clé partagée | Confidentialité, non-répudiation | HMAC-SHA256, Poly1305 | hash(clé ‖ message) (attaque par extension de longueur) |
| Chiffrement symétrique | Confidentialité | Intégrité (sauf mode AEAD) | AES-GCM, ChaCha20-Poly1305 (AEAD : chiffre et authentifie) | AES-ECB (motifs visibles) ; réutiliser un nonce |
| Échange de clés | Une clé partagée sur canal public | L’identité de l’interlocuteur | X25519 (ECDH) | Sans authentification : homme du milieu |
| Signature | Authenticité + non-répudiation avec clé publique | Confidentialité | Ed25519, ECDSA P-256, RSA-PSS | Signer un hachage rapide de mot de passe… (rien à voir) ; mauvais aléa (PlayStation 3) |
| Certificat | Lie une clé publique à une identité via une autorité | — | X.509, TLS 1.3 | Désactiver la vérification du certificat « pour tester » |
| Aléa | Imprévisibilité | — | os.urandom, secrets, TRNG matériel | random (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)
- Analyseur logique. Branchez un analyseur (Saleae, ou clone 8 canaux + PulseView) sur l’UART et l’I2C de votre carte. Capturez une transaction I2C avec l’IMU : identifiez START, adresse + R/W, ACK, données, STOP ; mesurez la fréquence d’horloge. Sur l’UART : vérifiez 8N1 et la vitesse. Capture d’écran annotée.
- Protocole binaire en C et Python. Implémentez côté carte l’encodeur/décodeur (sync, longueur, séquence, type, CRC-16 par table) et côté PC en Python (
struct,pyserial). Messages : télémétrie 50 Hz, commande, ping/pong. Testez : débranchement/rebranchement (resynchronisation), corruption (court-circuiter brièvement RX : CRC rejetés comptés), latence aller-retour (histogramme sur 1000 pings). - Fiabilité. Ajoutez acquittement + retransmission avec numéro de séquence pour les commandes seulement (pas pour la télémétrie) ; simulez 20 % de pertes côté PC (ignorer aléatoirement) et vérifiez qu’aucune commande n’est perdue ni dupliquée.
- Réseau. Le PC (ou un Raspberry Pi sur le robot) expose la télémétrie : serveur UDP (diffusion) + serveur TCP de commandes (L08) + MQTT vers un broker mosquitto. Wireshark : capturez les trois flux, comparez les en-têtes et les tailles totales pour 1000 messages.
- Sécurité. Authentifiez les commandes par HMAC-SHA256 avec compteur anti-rejeu (sur la carte : implémentation SHA-256 légère, ou mbedTLS sur ESP32) ; mesurez le coût CPU. Passez MQTT en MQTTS (TLS, certificat auto-signé) ; vérifiez dans Wireshark que le contenu est illisible. Tentez un rejeu d’une commande capturée : elle doit être refusée.
- Livrable. Dépôt : firmware, bibliothèque Python du protocole (avec tests par propriétés sur l’encodeur/décodeur : décoder(encoder(m)) = m, resynchronisation après bruit aléatoire), captures d’analyseur et Wireshark, histogramme de latence, mesures de coût du HMAC.
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.erreursAvec 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.varExercice 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
| Mot | Définition |
|---|---|
| UART / SPI / I2C / CAN | Bus série : asynchrone 2 fils / rapide 4 fils / adressé 2 fils / différentiel robuste multi-maître. |
| Endianness | Ordre des octets d’un nombre. |
| Trame | Unité de transmission : sync, longueur, charge, CRC. |
| CRC | Détection d’erreurs par division polynomiale. |
| Séquence / acquittement / fenêtre | Mécanismes de fiabilité (TCP). |
| TCP / UDP | Flux fiable ordonné / datagrammes sans garantie. |
| Socket | Interface de programmation réseau. |
| MQTT / DDS | Publish/subscribe par broker / distribué (ROS 2). |
| Hachage / HMAC / signature | Intégrité / authentification par clé partagée / par clé publique. |
| Nonce / rejeu | Valeur unique par message / attaque par renvoi. |
| TLS | Transport chiffré et authentifié par certificats. |
| Code correcteur | Redondance 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
- Installer Wireshark et observer une conversation MQTT, puis HTTPS (que voit-on encore ?).
- Lire les chapitres 1-3 de Kurose & Ross.
- Faire les exercices « Cryptopals » sets 1-2 (cryptographie par la pratique).