NSI · Terminale

Architectures, systèmes et réseaux : de la puce à HTTPS

Comment un appareil traite-t-il plusieurs tâches et échange-t-il des données ? Suis six décisions : répartir les fonctions d’une puce, partager le processeur, débloquer les ressources, choisir une route, comparer les coûts et établir un canal HTTPS. Les schémas rendent les mécanismes visibles ; les exemples Python permettent de vérifier chaque raisonnement.

Explications et exemples en accès libre. Ateliers, quiz et cartes avec l’abonnement.

Étude en accès libre
Environ 50 min à 1 h 35
Progression
6 étapes guidées
Vérification
12 questions
Rappel actif
12 cartes
Prévoir mon tempsExplications et exemples en accès libre

Une première étude des explications, schémas, exemples résolus et erreurs expliquées. Les activités, productions, quiz et cartes réservés à l’abonnement ne sont pas comptés ici.

Comprendre2507 mots d’explication et 6 schémas
17 à 28 min
Étudier les exemples et les erreurs18 cas, exemples et activités guidés
36 à 66 min

Étude du cours en accès libre, environ50 min à 1 h 35

Voir le calcul et le temps par étape

Le calcul suit automatiquement les explications et les tâches présentes dans le cours. Ses coefficients sont des repères de planification, pas des temps mesurés auprès d’élèves.

  • Lecture active : 2507 mots, à raison de 160 à 220 mots par minute.
  • Schémas : 6, avec 1 à 2 min pour lire chacun.
  • Exemple guidé : 6 × 2 à 4 min.
  • Suivre le code résolu et ses tests : 6 × 3 à 5 min.
  • Comprendre une erreur expliquée : 6 × 1 à 2 min.
  1. Système sur puce : identifier les blocs, puis choisir8 à 16 min
  2. Processus : plusieurs tâches, un processeur à partager9 à 16 min
  3. Ressources : attendre n’est pas toujours s’interbloquer8 à 16 min
  4. RIP : apprendre une destination grâce à ses voisins8 à 16 min
  5. OSPF : une carte partagée, un calcul local8 à 16 min
  6. HTTPS : établir un secret et vérifier avec qui l’on échange9 à 16 min

Les sous-totaux sont arrondis à la minute, puis additionnés. La fourchette totale est élargie aux cinq minutes voisines. Les étapes ci-dessus comprennent leurs explications et exemples ; elles ne s’ajoutent pas une seconde fois au total.

Adapte ce repère à tes acquis et au soin apporté aux exercices. Les pauses, les reprises, la consultation des sources externes et les révisions suivantes s’ajoutent selon tes besoins.

Avec les ateliers, le projet, le quiz et les cartes : environ 2 h 50 à 4 h 55, à répartir sur plusieurs séances.

Objectifs du cours

Ce que tu vas savoir faire

  • Identifier les composants d’un SoC et justifier un choix sous plusieurs contraintes.
  • Décrire la création d’un processus, ses états et son passage sur le processeur.
  • Reconnaître un interblocage à partir des ressources détenues et attendues.
  • Construire une table à vecteur de distance et suivre ses prochains sauts.
  • Expliquer le calcul local d’un chemin de coût minimal à partir de l’état des liens.
  • Distinguer clé partagée, paire de clés, certificat et protection d’un échange HTTPS.
01

Étape du cours · 8 à 16 min

Système sur puce : identifier les blocs, puis choisir

Un système sur puce, ou SoC, rassemble plusieurs fonctions dans un même circuit intégré. Sur le schéma, repère le processeur qui exécute des instructions, la mémoire locale, un accélérateur spécialisé et les contrôleurs qui communiquent avec l’extérieur. L’interconnexion fait circuler les informations entre ces blocs. Le processeur n’est donc qu’un composant du système, pas un synonyme de la puce entière.

Le contour de la puce compte autant que les blocs. Une mémoire vive externe, un écran ou un capteur peuvent rester hors du SoC tout en étant commandés par ses interfaces. L’intégration raccourcit certains échanges et peut réduire l’encombrement et l’énergie consommée. Elle ne signifie ni que toutes les mémoires sont internes ni que toute opération devient automatiquement plus rapide.

Pour traiter une image, on peut exécuter un programme sur un processeur généraliste ou confier une opération répétée à un accélérateur. La première solution se modifie facilement par le logiciel. La seconde peut gagner en vitesse et en énergie sur sa tâche, au prix d’une spécialisation, d’une surface et d’un effort de conception supplémentaires. Avant de comparer, impose le même résultat, les mêmes données et les mêmes conditions de fonctionnement.

Nos deux profils fictifs décrivent cette même tâche : surface en mm², énergie par image en µJ et latence en µs. La latence est le délai de traitement d’une image ; le débit serait le nombre d’images traitées par unité de temps. On n’additionne pas les latences de tous les blocs d’une puce : des opérations peuvent se recouvrir. Ici, chaque profil donne directement les trois mesures globales de l’architecture étudiée.

Le cahier des charges impose trois plafonds simultanés. Avec 18 mm², 40 µJ et 20 µs, le profil spécialisé respecte les trois ; le profil souple échoue sur l’énergie et le délai. Si plusieurs candidats passent, la décision nécessite une priorité supplémentaire, comme la facilité de modification. Le transfert recherche les profils non dominés : aucun autre n’est au moins aussi bon partout et strictement meilleur sur un critère.

Voir pour comprendre

Dans la puce, des fonctions reliées

Schéma fonctionnel d’un SoC fictif. Les surfaces dessinées ne représentent pas la taille des composants.

Un même circuit intégré

Interconnexions internes

  • CPUExécuter les instructions
  • AccélérateurTraiter une tâche spécialisée
  • Mémoire localeConserver données et instructions
  • Contrôleur mémoireDialoguer avec la RAM externe
  • Contrôleur d’E/SÉchanger avec les périphériques
  • Horloge / commandePiloter le fonctionnement

Liaisons via les contrôleurs

  • RAM externe
  • Capteur / écran

Hors de la puce dans cet exemple

Lis le schéma. Repère le contour de la puce, les six blocs et leur interconnexion. Les deux éléments du bas sont externes.

Le CPU exécute des instructions ; les autres blocs assurent des fonctions complémentaires.

Schéma original Maxdecours, sans photographie de composant commercial. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

Deux architectures, un seul cahier des charges

  1. Le profil souple vaut (12 mm², 90 µJ, 48 µs) ; le spécialisé vaut (18 mm², 35 µJ, 14 µs).
  2. Compare chaque mesure au plafond correspondant (18, 40, 20). Pour souple : vrai, faux, faux.
  3. Pour spécialisé : vrai, vrai, vrai. Le test global utilise « et », jamais « ou ».
  4. Si le plafond de surface descend à 17 mm², aucun profil ne passe. Cela ne rend pas les autres contraintes facultatives.

Conclusion. Un refus explique quelle exigence manque ; il ne désigne pas un matériel universellement mauvais.

Laboratoire de code

Languepython

ButSélectionner des profils matériels fictifs sous trois contraintes, sans mélanger les unités.

Code solution
CRITERES = ('surface_mm2', 'energie_uj', 'latence_us')

def verifier_profil(profil):
    if type(profil) is not dict or set(profil) != set(CRITERES):
        raise ValueError('trois critères nommés attendus')
    if any(type(profil[c]) is not int or not 1 <= profil[c] <= 10000
           for c in CRITERES):
        raise ValueError('mesures entières entre 1 et 10000 attendues')

def admissible(profil, budget):
    verifier_profil(profil)
    verifier_profil(budget)
    return all(profil[c] <= budget[c] for c in CRITERES)

def choisir_profils(profils, budget):
    verifier_profil(budget)
    if type(profils) is not dict:
        raise ValueError('dictionnaire de profils attendu')
    for nom, profil in profils.items():
        if type(nom) is not str or not nom:
            raise ValueError('nom non vide attendu')
        verifier_profil(profil)
    return sorted(nom for nom, profil in profils.items()
                  if admissible(profil, budget))

Tests

profils = {
    'souple': {'surface_mm2': 12, 'energie_uj': 90, 'latence_us': 48},
    'specialise': {'surface_mm2': 18, 'energie_uj': 35, 'latence_us': 14},
}
budget = {'surface_mm2': 18, 'energie_uj': 40, 'latence_us': 20}
assert choisir_profils(profils, budget) == ['specialise']
assert choisir_profils({}, budget) == []
assert choisir_profils(profils, dict(budget, surface_mm2=17)) == []
assert admissible(budget, budget)
for valeur in (True, 0, -1, 15.5, float('nan'), float('inf')):
    try:
        admissible(dict(budget, energie_uj=valeur), budget)
    except ValueError:
        pass
    else:
        raise AssertionError('mesure invalide acceptée')
assert profils['souple']['energie_uj'] == 90

Trace

  • Chaque mesure est associée à son unité et validée avant la comparaison.
  • Souple échoue sur deux plafonds ; spécialisé respecte les trois.
  • Avec une surface maximale de 17 mm², la liste des candidats devient vide.

Clinique de bogue

Indice observéLe profil souple est accepté malgré 90 µJ d’énergie pour un maximum de 40 µJ.

Causeany se contente d’une seule condition vraie : la petite surface masque les deux dépassements. Le cahier des charges exige les trois critères nommés et simultanément respectés. Valider leur présence évite aussi qu’une clé oubliée fasse disparaître une contrainte.

02

Étape du cours · 9 à 16 min

Processus : plusieurs tâches, un processeur à partager

Un programme est un ensemble d’instructions ; un processus est une exécution de ce programme avec son état propre. Deux exécutions peuvent donc avoir le même code mais des données et des identifiants différents. Le système d’exploitation prépare notamment l’espace mémoire et le contexte d’exécution, puis organise l’accès au processeur. Lors d’un changement de tâche, il conserve les informations nécessaires pour reprendre le travail.

Un processus peut en créer un autre par une demande au système. Son PID l’identifie ; le PPID désigne son parent dans les systèmes qui exposent cette relation. Les identifiants peuvent être réutilisés : ne déduis pas une chronologie certaine de leurs seuls numéros. Sous Linux, la commande en lecture seule ps -eo pid,ppid,stat donne un instantané sans les arguments des programmes. Repère un PID repris comme PPID d’une autre ligne : il identifie le parent de ce processus. Garde le relevé sur ta machine ; pour l’entraînement fourni, on utilise uniquement les tâches fictives P1 et P2.

Dans notre modèle, un processus prêt peut utiliser le processeur mais attend son tour. Actif, il exécute ses instructions. Bloqué, il attend un événement, par exemple la fin d’une lecture. Cet événement le rend prêt ; l’ordonnanceur décide ensuite de l’élire. Un processus terminé ne revient pas dans la file. Les états précis d’un outil système sont plus détaillés : sous Linux, R regroupe notamment l’exécution et l’attente du processeur.

Sur un processeur logique, notre modèle n’exécute qu’une tâche à la fois. L’alternance donne une exécution concurrente ; plusieurs cœurs peuvent permettre un véritable parallélisme. Avec le tourniquet, chaque tâche reçoit au plus un quantum, puis retourne en fin de file s’il reste du travail. Elle ne doit ni monopoliser le début de la file ni consommer un quantum complet quand elle peut finir plus tôt.

P1 demande cinq unités de calcul et P2 en demande trois. Avec un quantum de deux, la frise donne P1, P2, P1, P2, P1 : les deux derniers créneaux ne durent qu’une unité. La durée totale vaut huit. Le laboratoire suppose toutes les tâches prêtes à zéro, sans entrée-sortie ni coût de commutation ; la fonction séparée etat_suivant étudie les transitions, mais ne transforme pas la frise en simulateur d’entrées-sorties.

Le temps de réponse étudié va de l’arrivée au premier passage ; le temps d’attente cumule les moments prêts mais non actifs. Dans ce modèle sans blocage et avec arrivée à zéro, attente = date de fin − durée de calcul. Un petit quantum peut réduire la première attente, mais multiplie les changements de contexte sur une machine réelle. La file Python utilise pop(0) pour rester lisible ; ses décalages ne sont pas un coût de processeur simulé.

Voir pour comprendre

Une file devient une frise de calcul

Deux tâches prêtes à zéro ; un processeur logique ; aucun temps de commutation.

Quantum 2 · P1 : 5 unités · P2 : 3 unités

Créneaux calculés du tourniquet P1 de 0 à 2, reste 3. P2 de 2 à 4, reste 1. P1 de 4 à 6, reste 1. P2 de 6 à 7, reste 0. P1 de 7 à 8, reste 0. P1 0 P2 2 P1 4 P2 6 P1 7 8 Temps (unités de calcul)

Fin de P1 : 8 · Fin de P2 : 7 · Total : 8 unités.

Lis le schéma. Lis de gauche à droite. La largeur de chaque créneau représente sa durée ; le nom indique qui calcule.

Un quantum est un maximum, pas une durée à consommer obligatoirement.

Schéma original Maxdecours calculé depuis les deux durées et le quantum. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

Tracer huit unités, sans en créer une neuvième

  1. À t = 0, la file est [P1:5, P2:3]. P1 calcule de 0 à 2 ; il lui reste 3.
  2. La file devient [P2:3, P1:3]. P2 calcule de 2 à 4 ; il lui reste 1.
  3. P1 calcule de 4 à 6 ; il lui reste 1. P2 termine de 6 à 7 et quitte la file.
  4. P1 termine de 7 à 8. P2 a attendu 7 − 3 = 4 unités ; sa première réponse survient à t = 2.

Conclusion. La somme des créneaux égale exactement 5 + 3, et aucune tâche terminée n’est réinsérée.

Laboratoire de code

Languepython

ButTracer un tourniquet fini et vérifier séparément les transitions de processus.

Code solution
def verifier_taches(processus, quantum):
    if type(quantum) is not int or not 1 <= quantum <= 100:
        raise ValueError('quantum entier entre 1 et 100 attendu')
    if type(processus) is not list or len(processus) > 20:
        raise ValueError('liste de 0 à 20 tâches attendue')
    noms = set()
    for tache in processus:
        if type(tache) not in (tuple, list) or len(tache) != 2:
            raise ValueError('couple nom, durée attendu')
        nom, duree = tache
        if type(nom) is not str or not nom or nom in noms:
            raise ValueError('noms non vides et distincts attendus')
        if type(duree) is not int or not 1 <= duree <= 100:
            raise ValueError('durée entière entre 1 et 100 attendue')
        noms.add(nom)

def tourniquet(processus, quantum):
    verifier_taches(processus, quantum)
    file = [(nom, duree) for nom, duree in processus]
    temps, trace = 0, []
    while file:
        nom, reste = file.pop(0)
        utilise = min(quantum, reste)
        restant = reste - utilise
        trace.append((temps, temps + utilise, nom, restant))
        temps += utilise
        if restant > 0:
            file.append((nom, restant))
    return trace

TRANSITIONS = {
    ('nouveau', 'admettre'): 'pret',
    ('pret', 'elire'): 'actif',
    ('actif', 'quantum'): 'pret',
    ('actif', 'attendre'): 'bloque',
    ('bloque', 'reveiller'): 'pret',
    ('actif', 'finir'): 'termine',
}

def etat_suivant(etat, evenement):
    if type(etat) is not str or type(evenement) is not str:
        raise ValueError('état et événement textuels attendus')
    if (etat, evenement) not in TRANSITIONS:
        raise ValueError('transition non prévue dans ce modèle')
    return TRANSITIONS[(etat, evenement)]

Tests

taches = [('P1', 5), ('P2', 3)]
assert tourniquet(taches, 2) == [
    (0, 2, 'P1', 3), (2, 4, 'P2', 1), (4, 6, 'P1', 1),
    (6, 7, 'P2', 0), (7, 8, 'P1', 0)]
assert taches == [('P1', 5), ('P2', 3)]
assert tourniquet([], 2) == []
assert tourniquet([('P1', 1)], 2) == [(0, 1, 'P1', 0)]
assert etat_suivant('bloque', 'reveiller') == 'pret'
for t, q in [([('P1', 3)], True), ([('P1', float('nan'))], 2),
             ([('P1', 2), ('P1', 3)], 2)]:
    try:
        tourniquet(t, q)
    except ValueError:
        pass
    else:
        raise AssertionError('contrat invalide accepté')
try:
    etat_suivant('bloque', 'elire')
except ValueError:
    pass
else:
    raise AssertionError('élection depuis bloqué acceptée')

Trace

  • P1 laisse trois unités et rejoint la fin ; P2 devient la nouvelle tête.
  • P2 termine à 7, puis P1 à 8, avec des derniers créneaux de durée 1.
  • Un booléen ou NaN est refusé avant de modifier une file de travail.

Clinique de bogue

Indice observéAprès son premier quantum, P1 repasse à chaque tour ; P2 n’obtient le processeur qu’après la fin de P1.

CauseRéinsérer en position zéro recrée la même tête de file et fait attendre les autres tâches. Le tourniquet exige une réinsertion à la fin, uniquement si le reliquat est positif. La correction valide aussi le quantum et les durées avant de calculer ce reliquat.

03

Étape du cours · 8 à 16 min

Ressources : attendre n’est pas toujours s’interbloquer

P1 utilise une ressource dont P2 a besoin. P2 attend, mais si P1 finit son travail et libère la ressource, cette attente a une issue. Un interblocage apparaît lorsque des tâches ne peuvent plus avancer parce qu’elles attendent mutuellement des ressources qu’elles conservent. Pour comprendre la situation, écris d’abord qui détient chaque ressource et qui demande quoi.

Considérons deux verrous exclusifs, chacun disponible en un seul exemplaire : R1 et R2. P1 détient R1 et demande R2 ; P2 détient R2 et demande R1. Les deux tâches attendent tout en conservant leur premier verrou. Sans libération forcée ni autre intervention, aucune ne peut atteindre l’instruction qui libérerait ce que l’autre réclame. On a une attente circulaire.

Le graphe d’attente représente les processus, pas les ressources : une flèche P1 → P2 signifie que P1 attend une ressource détenue par P2. Le cycle P1 → P2 → P1 révèle ici l’interblocage. Le critère est lié à nos hypothèses : ressources à une instance, exclusives, non retirables de force et demandes nécessaires pour poursuivre. On ne transpose pas automatiquement ce test à des ressources disponibles en plusieurs exemplaires.

Les verrous étudiés sont non réentrants : les demander à nouveau quand on les détient déjà peut bloquer la même tâche. Le graphe conserve donc P1 → P1, témoin d’auto-interblocage. Une ressource libre, notée None, ne crée au contraire aucune flèche. Une ressource inconnue est une erreur d’entrée, pas une ressource supposée libre ; le laboratoire la refuse.

La recherche de cycle distingue les sommets déjà visités de ceux encore présents dans le chemin en cours. Rejoindre un sommet du chemin ferme un cycle ; rejoindre un sommet complètement exploré ne suffit pas. Pour prévenir l’attente circulaire, toutes les tâches peuvent respecter le même ordre strict d’acquisition, R1 avant R2. Cet ordre retire une condition de l’interblocage ; il ne prouve pas que chaque tâche sera servie rapidement.

Voir pour comprendre

Deux tâches qui se retiennent mutuellement

Chaque verrou est unique, exclusif et conservé pendant l’attente.

  1. P1 détient R1P1 demande R2, déjà détenue par P2 : il attend P2.
  2. P2 détient R2P2 demande R1, déjà détenue par P1 : il attend P1.
  3. Retour à P1Le cycle est fermé. Les deux verrous restent détenus pendant l’attente.

Lis le schéma. Suis le chemin jusqu’au retour au premier processus. Chaque passage nomme la ressource qui manque.

P1 → P2 → P1 : aucun des deux ne peut progresser pour libérer son verrou.

Schéma original Maxdecours, allocation entièrement fictive. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

La ressource explique la flèche

  1. R1 appartient à P1 ; R2 appartient à P2. P1 demande R2 : trace P1 → P2.
  2. P2 demande R1 : trace P2 → P1. Suivre les flèches ramène à P1.
  3. Si P2 ne demande rien, la seconde flèche disparaît. P2 peut finir et libérer R2.
  4. Si tous acquièrent R1 avant R2, P2 ne doit pas garder R2 en réclamant ensuite R1. L’ordre interdit précisément cette situation.

Conclusion. Le dessin est une conséquence du tableau d’allocation, pas une impression de lenteur.

Laboratoire de code

Languepython

ButConstruire un graphe d’attente et produire un cycle explicite, y compris une attente sur soi.

Code solution
def graphe_attente(detenteurs, demandes):
    if type(detenteurs) is not dict or type(demandes) is not dict:
        raise ValueError('deux dictionnaires attendus')
    if len(detenteurs) > 20 or len(demandes) > 20:
        raise ValueError('modèle limité à 20 ressources et 20 demandeurs')
    nom_valide = lambda x: type(x) is str and bool(x)
    if any(not nom_valide(r) or (p is not None and not nom_valide(p))
           for r, p in detenteurs.items()):
        raise ValueError('ressource nommée et détenteur nommé ou None attendus')
    for p, ressources in demandes.items():
        if (not nom_valide(p) or type(ressources) not in (set, frozenset)
                or not ressources <= detenteurs.keys()):
            raise ValueError('demandeur nommé et ressources connues attendus')
    processus = set(demandes) | {p for p in detenteurs.values() if p is not None}
    if len(processus) > 20:
        raise ValueError('modèle limité à 20 processus')
    graphe = {p: set() for p in sorted(processus)}
    for p, ressources in demandes.items():
        for r in ressources:
            proprietaire = detenteurs[r]
            if proprietaire is not None:
                graphe[p].add(proprietaire)
    return graphe

def trouver_cycle(graphe):
    if (type(graphe) is not dict or len(graphe) > 20
            or any(type(p) is not str or not p for p in graphe)):
        raise ValueError('graphe de 0 à 20 processus nommés attendu')
    for voisins in graphe.values():
        if type(voisins) not in (set, frozenset) or not voisins <= graphe.keys():
            raise ValueError('successeurs connus attendus')
    visites, actifs, chemin = set(), {}, []

    def visiter(p):
        visites.add(p)
        actifs[p] = len(chemin)
        chemin.append(p)
        for q in sorted(graphe[p]):
            if q in actifs:
                return chemin[actifs[q]:] + [q]
            if q not in visites:
                cycle = visiter(q)
                if cycle:
                    return cycle
        chemin.pop()
        del actifs[p]
        return []

    for p in sorted(graphe):
        if p not in visites:
            cycle = visiter(p)
            if cycle:
                return cycle
    return []

Tests

detenteurs = {'R1': 'P1', 'R2': 'P2'}
demandes = {'P1': {'R2'}, 'P2': {'R1'}}
g = graphe_attente(detenteurs, demandes)
assert g == {'P1': {'P2'}, 'P2': {'P1'}}
assert trouver_cycle(g) == ['P1', 'P2', 'P1']
assert trouver_cycle({'P1': {'P2'}, 'P2': set()}) == []
assert trouver_cycle(graphe_attente({'R1': 'P1'}, {'P1': {'R1'}})) == ['P1', 'P1']
assert trouver_cycle(graphe_attente({'R1': None}, {'P1': {'R1'}})) == []
assert detenteurs == {'R1': 'P1', 'R2': 'P2'}
try:
    graphe_attente(detenteurs, {'P1': {'R9'}})
except ValueError:
    pass
else:
    raise AssertionError('ressource inconnue acceptée')

Trace

  • R2 détenue par P2 produit P1 → P2 ; R1 détenue par P1 produit P2 → P1.
  • Le retour vers P1 encore actif dans la recherche fournit le cycle fermé.
  • Sans la demande de P2, le chemin se termine et aucun cycle n’est trouvé.

Clinique de bogue

Indice observéUne simple attente de P1 vers P2 est annoncée comme un interblocage.

CauseTester l’existence d’au moins une arête confond attente et cycle. P2 peut ne rien attendre et finir. Il faut chercher un retour vers un sommet du chemin de recherche en cours, et non simplement un sommet visité auparavant dans une autre branche.

04

Étape du cours · 8 à 16 min

RIP : apprendre une destination grâce à ses voisins

Un routeur transmet un paquet vers une destination en consultant sa table de routage. Une ligne indique notamment la destination, le prochain saut et une métrique. Le prochain saut est un voisin, pas le chemin entier. Des routes peuvent être configurées manuellement ; un protocole de routage permet aussi de les actualiser par échanges entre routeurs. RIP et OSPF sont des protocoles de routage interne à un système autonome.

Dans RIP, les routeurs échangent des estimations de distance avec leurs voisins. Avec un coût d’un saut par liaison, si B annonce D à distance 2, A calcule une possibilité de distance 3 via B. A conserve une meilleure possibilité pour chaque destination. Le nombre de sauts est la métrique étudiée : un chemin plus court en sauts n’est pas nécessairement plus rapide en secondes.

Les métriques utilisables sont limitées à 15 ; 16 signifie inaccessible. La distance candidate est donc plafonnée à 16. Dans notre graphe de routeurs, un routeur se connaît à distance zéro et ses voisins à distance un. Les réseaux destinataires et les interfaces d’une vraie table sont ici représentés par des noms de routeurs pour isoler le calcul. Ne transforme pas la valeur spéciale 16 en une route à emprunter.

Sur A–B–C–D, A connaît d’abord B, pas encore C ni D. Après un échange, il apprend C via B ; après le suivant, D via B. Chaque tour de simulation lit uniquement les tables du tour précédent, puis remplace toutes les tables ensemble. Cela rend l’expérience reproductible et permet de compter les tours. À égalité, notre modèle choisit le voisin au nom le plus petit : c’est sa convention, pas une règle universelle de RIP.

Une panne n’efface pas instantanément les anciennes annonces de toutes les machines. Sans précautions, deux routeurs peuvent croire chacun que l’autre connaît une route disparue et faire monter la métrique jusqu’à 16 : le comptage à l’infini. Des mécanismes comme l’horizon partagé et les annonces inversées empoisonnées réduisent certaines boucles. Le laboratoire calcule une topologie fixe ; le transfert retire un lien et recommence depuis zéro pour comparer les routes possibles, sans simuler les délais réels de convergence.

Voir pour comprendre

La table de A s’enrichit, tour après tour

Topologie en ligne A–B–C–D. Toutes les liaisons valent un saut.

La table de A s’enrichit, tour après tour
Table de AVers BVers CVers D
Initiale1 / B16 / aucun16 / aucun
Échange 11 / B2 / B16 / aucun
Échange 21 / B2 / B3 / B
Échange 31 / B2 / B3 / B

Lis le schéma. Une case donne distance / prochain saut. Le tour lit toutes les annonces de l’état précédent.

D est découvert au deuxième échange via B, même si A n’est pas voisin de D.

Table originale Maxdecours, états confrontés au modèle Python. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

A découvre D sans recevoir sa table directement

  1. Au départ, A connaît A à 0 et B à 1 ; C et D valent 16, sans prochain saut.
  2. Premier échange : B connaît C à 1. A obtient C à 2 via B, mais pas encore D.
  3. Deuxième échange : B connaît désormais D à 2. A obtient D à 3 via B.
  4. Pour acheminer le paquet, A consulte sa ligne D et envoie à B. B poursuit vers C, puis C vers D.

Conclusion. Les tables propagent une connaissance de proche en proche ; chaque routeur prend sa décision locale.

Laboratoire de code

Languepython

ButFaire converger de petites tables synchrones et conserver les étapes, sans utiliser le réseau réel.

Code solution
def verifier_reseau(graphe):
    if (type(graphe) is not dict or len(graphe) > 20
            or any(type(s) is not str or not s for s in graphe)):
        raise ValueError('graphe de 0 à 20 routeurs nommés attendu')
    for s, voisins in graphe.items():
        if (type(voisins) not in (set, frozenset)
                or not voisins <= graphe.keys() or s in voisins):
            raise ValueError('voisins connus et sans boucle attendus')
    if any(s not in graphe[v] for s in graphe for v in graphe[s]):
        raise ValueError('liaisons symétriques attendues')

def distance_candidate(distance):
    if type(distance) is not int or not 0 <= distance <= 16:
        raise ValueError('métrique entière de 0 à 16 attendue')
    return min(16, distance + 1)

def rip(graphe):
    verifier_reseau(graphe)
    noms = sorted(graphe)
    tables = {s: {d: ((0, None) if d == s else
                     (1, d) if d in graphe[s] else (16, None))
                  for d in noms} for s in noms}
    copier = lambda t: {s: dict(ligne) for s, ligne in t.items()}
    histoire = [copier(tables)]
    for _ in range(len(noms)):
        suivantes = {}
        for s in noms:
            suivantes[s] = {}
            for d in noms:
                if d == s:
                    suivantes[s][d] = (0, None)
                    continue
                candidats = [(distance_candidate(tables[v][d][0]), v)
                             for v in sorted(graphe[s])]
                distance, voisin = min(candidats, default=(16, None))
                suivantes[s][d] = ((distance, voisin) if distance < 16
                                   else (16, None))
        histoire.append(copier(suivantes))
        if suivantes == tables:
            return suivantes, histoire
        tables = suivantes
    return tables, histoire

Tests

g = {'A': {'B'}, 'B': {'A', 'C'}, 'C': {'B', 'D'}, 'D': {'C'}}
tables, histoire = rip(g)
assert histoire[0]['A']['D'] == (16, None)
assert histoire[1]['A']['C'] == (2, 'B')
assert histoire[1]['A']['D'] == (16, None)
assert histoire[2]['A']['D'] == (3, 'B')
assert tables['A']['D'] == (3, 'B')
assert tables['D']['A'] == (3, 'C')
assert histoire[-1] == histoire[-2]
assert rip({'A': set(), 'B': set()})[0]['A']['B'] == (16, None)
assert distance_candidate(15) == distance_candidate(16) == 16
assert g['A'] == {'B'}

Trace

  • Table initiale : A connaît seulement lui-même et B.
  • Au premier échange, A apprend C ; au deuxième, il apprend D via B.
  • Un dernier échange identique constate la stabilité, sans être compté comme une nouvelle route.

Clinique de bogue

Indice observéB annonce D à distance 2 et A conserve aussi 2, en oubliant son trajet vers B.

CauseLa distance annoncée part du voisin B, pas du routeur A. Il manque le coût du premier saut A → B. La correction ajoute un, plafonne à 16 et refuse les valeurs non entières ou déjà hors du domaine des métriques.

05

Étape du cours · 8 à 16 min

OSPF : une carte partagée, un calcul local

Avec un protocole à état de liens, les routeurs diffusent des informations sur leurs liaisons. Dans une même zone OSPF stabilisée, chacun dispose d’une base décrivant la topologie de la zone. Chaque routeur effectue ensuite son propre calcul en prenant sa position comme départ. Il ne reçoit donc pas simplement une distance finale du voisin comme dans le modèle à vecteur de distance.

Le coût d’une route est la somme des coûts de ses liens. Ces valeurs configurables sont sans unité et peuvent être choisies à partir du débit des interfaces ; elles ne mesurent pas directement la latence du trajet. Avec une référence choisie de 100 Mbit/s, la règle d’exercice référence / débit donne 1 pour 100 Mbit/s et 10 pour 10 Mbit/s. Il faut conserver les mêmes unités dans le rapport. Cette formule est un choix de configuration, pas l’unique définition d’OSPF.

Sur l’anneau A–B–C–D–A, AB, BC et CD coûtent chacun 1 ; AD coûte 10. En nombre de sauts, A rejoint D directement. En somme des coûts, le chemin A–B–C–D coûte 3 et gagne contre 10. Le dessin juxtapose les deux décisions sur les mêmes liaisons. La longueur d’un trait sur la page n’a aucune valeur dans le calcul.

L’algorithme de Dijkstra part de la source à distance zéro et des autres sommets à l’infini. Il choisit parmi les sommets non fixés celui de distance provisoire minimale, le fixe, puis essaie d’améliorer ses voisins. La positivité des coûts permet de rendre cette distance définitive. Notre implantation simple balaie les candidats à chaque tour ; elle illustre le raisonnement, pas l’organisation complète d’un routeur.

Le laboratoire représente des liaisons symétriques de coût entier strictement positif et vérifie toute la carte, y compris une composante inaccessible. La source doit exister. Les égalités sont départagées de façon reproductible par les noms lors du choix des sommets ; une distance égale ne remplace pas un prédécesseur déjà trouvé. OSPF peut réellement conserver plusieurs routes de même coût. Si aucune route n’existe, la distance reste infinie et le chemin reconstruit est vide.

Voir pour comprendre

Même réseau, deux chemins préférés

Les étiquettes sont des coûts configurés, pas des secondes. Le nombre de traits parcourus donne les sauts.

Minimum de sauts

Minimum de sauts, A vers D AB coûte 1. BC coûte 1. CD coûte 1. AD coûte 10. Le chemin retenu a 1 liaison(s) et coûte 10. 1 1 1 10 A B C D

A → D
1 saut(s) · coût 10

Minimum de coût

Minimum de coût, A vers B vers C vers D AB coûte 1. BC coûte 1. CD coûte 1. AD coûte 10. Le chemin retenu a 3 liaison(s) et coûte 3. 1 1 1 10 A B C D

A → B → C → D
3 saut(s) · coût 3

Lis le schéma. À gauche, minimise les sauts ; à droite, additionne les coûts. Les chemins retenus sont épaissis et nommés sous chaque dessin.

A–D gagne en sauts ; A–B–C–D gagne en coût total.

Schéma original Maxdecours, chemins calculés depuis les quatre coûts canoniques. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

Dijkstra de A vers D, en quatre décisions

  1. Fixe A à 0. Propose B à 1 et D à 10 ; C reste à l’infini.
  2. Fixe B à 1, puis améliore C à 2 par B. D reste provisoirement à 10.
  3. Fixe C à 2, puis améliore D à 3 par C. Le trajet direct n’est plus le meilleur.
  4. Fixe D à 3. Ses prédécesseurs reconstruisent D ← C ← B ← A, donc le chemin A–B–C–D.

Conclusion. Une valeur provisoire peut s’améliorer ; on ne choisit jamais de nouveau un sommet déjà fixé.

Laboratoire de code

Languepython

ButCalculer les coûts minimaux, conserver une trace et reconstruire un chemin sur une carte validée.

Code solution
# Carte non orientée, coûts entiers de 1 à 100 ; source présente.
def verifier_reseau_pondere(graphe):
    if (type(graphe) is not dict or len(graphe) > 20
            or any(type(s) is not str or not s for s in graphe)):
        raise ValueError('graphe de 0 à 20 routeurs nommés attendu')
    for s, voisins in graphe.items():
        if type(voisins) is not dict or not voisins.keys() <= graphe.keys() or s in voisins:
            raise ValueError('voisins connus et sans boucle attendus')
        if any(type(c) is not int or not 1 <= c <= 100 for c in voisins.values()):
            raise ValueError('coûts entiers de 1 à 100 attendus')
    if any(graphe[v].get(s) != c for s in graphe for v, c in graphe[s].items()):
        raise ValueError('coûts symétriques attendus')

def verifier_source(graphe, source):
    if type(source) is not str or source not in graphe:
        raise ValueError('source présente dans le graphe attendue')

def dijkstra(graphe, source):
    verifier_reseau_pondere(graphe)
    verifier_source(graphe, source)
    distances = {s: float('inf') for s in graphe}
    precedents = {s: None for s in graphe}
    distances[source] = 0
    restants, trace = set(graphe), []
    while restants:
        u = min(restants, key=lambda s: (distances[s], s))
        if distances[u] == float('inf'):
            break
        restants.remove(u)
        for v in sorted(graphe[u]):
            candidat = distances[u] + graphe[u][v]
            if v in restants and candidat < distances[v]:
                distances[v] = candidat
                precedents[v] = u
        trace.append((u, dict(distances)))
    return distances, precedents, trace

def chemin(precedents, source, destination):
    if type(precedents) is not dict:
        raise ValueError('dictionnaire de prédécesseurs attendu')
    verifier_source(precedents, source)
    verifier_source(precedents, destination)
    if (len(precedents) > 20 or
            any(type(s) is not str or not s for s in precedents) or
            any(p is not None and (type(p) is not str or p not in precedents)
                for p in precedents.values()) or precedents[source] is not None):
        raise ValueError('prédécesseurs connus et source sans prédécesseur attendus')
    resultat, vus, u = [], set(), destination
    while u is not None:
        if u in vus:
            raise ValueError('cycle dans les prédécesseurs')
        vus.add(u)
        resultat.append(u)
        if u == source:
            return list(reversed(resultat))
        u = precedents[u]
    return []

Tests

g = {'A': {'B': 1, 'D': 10}, 'B': {'A': 1, 'C': 1},
     'C': {'B': 1, 'D': 1}, 'D': {'A': 10, 'C': 1}}
distances, precedents, trace = dijkstra(g, 'A')
assert distances == {'A': 0, 'B': 1, 'C': 2, 'D': 3}
assert chemin(precedents, 'A', 'D') == ['A', 'B', 'C', 'D']
assert [s for s, _ in trace] == ['A', 'B', 'C', 'D']
assert trace[0][1]['D'] == 10 and trace[2][1]['D'] == 3
distances, precedents, _ = dijkstra({'A': {}, 'B': {}}, 'A')
assert distances['B'] == float('inf')
assert chemin(precedents, 'A', 'B') == []
assert chemin(precedents, 'A', 'A') == ['A']
try:
    dijkstra(g, 'Z')
except ValueError:
    pass
else:
    raise AssertionError('source absente acceptée')

Trace

  • A propose le lien direct à 10 et le voisin B à 1.
  • B puis C permettent d’améliorer D jusqu’à 3.
  • D est fixé une fois ; la chaîne des prédécesseurs donne le trajet choisi.

Clinique de bogue

Indice observéL’algorithme sélectionne encore A, déjà fixé, et ne progresse plus vers B.

CauseLe minimum est recherché dans tout le dictionnaire, y compris la source dont la distance reste zéro. La sélection doit exclure les sommets fixés ; lorsqu’il n’en reste aucun, elle renvoie None. Ce filtrage rend possible l’avancement de l’ensemble fixé.

06

Étape du cours · 9 à 16 min

HTTPS : établir un secret et vérifier avec qui l’on échange

Le chiffrement symétrique utilise un secret partagé pour chiffrer et déchiffrer. Dans le chiffrement asymétrique à clé publique, on chiffre pour le destinataire avec sa clé publique ; sa clé privée permet de déchiffrer. Une signature a un autre rôle : la clé privée signe, la clé publique vérifie. La confidentialité d’un message et l’authentification de son auteur sont donc des propriétés différentes.

Un échange de clés établit des secrets communs sans les envoyer en clair. Il faut aussi authentifier l’autre partie, sinon un intermédiaire pourrait établir deux échanges séparés. Pour HTTPS, le certificat lie notamment une identité de serveur à une clé publique. Le client vérifie le nom demandé, la période et la chaîne de confiance, puis la preuve que le serveur possède la clé privée correspondante. Une signature valide sur un certificat d’un autre nom ne suffit pas.

Le schéma suit une connexion TLS 1.3 avec échange éphémère et certificat serveur, sans reprise ni données anticipées. ClientHello et ServerHello permettent de choisir les paramètres et de dériver des clés pour la négociation. Le serveur transmet ensuite ses paramètres, son certificat et sa preuve de possession, protégés pendant l’échange ; les messages Finished confirment notamment l’intégrité de la négociation. Les données applicatives utilisent ensuite des clés de trafic.

TLS 1.3 n’envoie pas une clé de session chiffrée par RSA comme le faisaient certains anciens modes. Le certificat peut porter une clé utilisée pour signer, tandis que l’établissement du secret utilise ici un échange éphémère. Les données sont chiffrées et authentifiées : une altération est détectée. Cela protège le canal, pas la véracité d’une page ni l’honnêteté de son auteur. HTTP sur TLS donne HTTPS ; les routeurs continuent d’acheminer les paquets.

Le laboratoire sépare volontairement deux mécanismes faciles à calculer : un tout petit exemple RSA transporte un nombre et XOR transforme les octets d’un texte. Les paramètres publics (3, 33) et privés (7, 33) sont des constantes d’exercice, pas des clés sûres. La lettre é et un emoji doivent d’abord être encodés en UTF-8 : un caractère Unicode n’est pas nécessairement un octet. Modifier un octet chiffré peut modifier le message sans alerte, ce qui montre l’absence d’intégrité de ce jouet.

La clinique de bogue rassemble des contrôles fictifs de certificat avec des noms réservés en .invalid. Les booléens de chaîne et de signature sont des résultats supposés, pas des vérifications cryptographiques réalisées par ce code. Leur intérêt est logique : tous les contrôles doivent réussir, et le nom doit correspondre entièrement. Pour une vraie communication, le logiciel s’appuie sur sa bibliothèque TLS et conserve ses vérifications ; aucun code de ce laboratoire ne sert à protéger des données.

Voir pour comprendre

TLS 1.3 : du premier échange aux données protégées

Connexion avec échange éphémère et certificat serveur. Le client attend la fin de la négociation avant ses données applicatives.

  1. ClientHello ↔ ServerHelloChoix des paramètres et échange de parts publiques éphémères ; dérivation des clés de négociation.
  2. Paramètres et certificatLe serveur les envoie sous protection. Le client contrôle nom, période et chaîne de confiance.
  3. Preuve et FinishedPreuve de possession de la clé privée ; confirmation de l’intégrité de la négociation par les messages Finished.
  4. Données HTTPSDes clés de trafic protègent les données avec un chiffrement authentifié.

Lis le schéma. Descends les étapes. Dériver une clé pour chiffrer la négociation ne suffit pas encore à authentifier le serveur.

Le secret n’est pas transmis tel quel ; l’authentification et la confirmation complètent son établissement.

Schéma original Maxdecours ; sélection du déroulement complet de la RFC 8446, sans reprise ni 0-RTT. · Source du repère · 06/09/2026

Exemples résolus et erreurs expliquées

Un message retrouvé n’est pas forcément protégé

  1. Choisis la clé jouet 7. Le calcul public 7³ modulo 33 donne 13 ; le calcul privé 13⁷ modulo 33 retrouve 7.
  2. Le texte A devient l’octet 65. XOR avec 7 donne 70 ; répéter XOR avec 7 redonne 65.
  3. Un tiers change 70 en 71. Le destinataire obtient alors 64, le caractère @, sans alerte du jouet.
  4. Dans TLS, le chiffrement authentifié détecte une telle altération. La réversibilité seule n’apporte donc pas les propriétés d’un canal sécurisé.

Conclusion. Le calcul illustre les rôles ; le contre-exemple explique pourquoi il ne faut pas confondre illustration et sécurité.

Laboratoire de code

Languepython

ButLire les rôles d’un transport de clé jouet et d’une transformation d’octets, puis constater leur absence d’intégrité.

Code solution
# Calculs pédagogiques uniquement : ces fonctions ne sécurisent aucune donnée.
def verifier_nombre_jouet(valeur):
    if type(valeur) is not int or not 0 <= valeur < 33:
        raise ValueError('entier de 0 à 32 attendu')

def chiffrer_nombre_jouet(valeur):
    verifier_nombre_jouet(valeur)
    return pow(valeur, 3, 33)

def dechiffrer_nombre_jouet(valeur):
    verifier_nombre_jouet(valeur)
    return pow(valeur, 7, 33)

def xor_octets(donnees, cle):
    if type(donnees) is not bytes or type(cle) is not int or not 0 <= cle <= 255:
        raise ValueError('octets et clé entière de 0 à 255 attendus')
    return bytes(octet ^ cle for octet in donnees)

def echange_jouet(texte, cle):
    if type(texte) is not str or type(cle) is not int or not 1 <= cle < 33:
        raise ValueError('texte et clé entière de 1 à 32 attendus')
    try:
        clair = texte.encode('utf-8')
    except UnicodeEncodeError:
        raise ValueError('texte encodable en UTF-8 attendu')
    cle_transmise = chiffrer_nombre_jouet(cle)
    cle_recue = dechiffrer_nombre_jouet(cle_transmise)
    chiffre = xor_octets(clair, cle)
    retour = xor_octets(chiffre, cle_recue).decode('utf-8')
    return cle_transmise, chiffre, retour

Tests

transport, chiffre, retour = echange_jouet('A', 7)
assert transport == 13 and chiffre == bytes([70]) and retour == 'A'
assert xor_octets(bytes([71]), 7) == b'@'
assert echange_jouet('é🙂', 7)[2] == 'é🙂'
assert len('é🙂'.encode('utf-8')) == 6
assert all(0 <= v <= 255 for v in echange_jouet('é🙂', 7)[1])
for valeur in range(33):
    assert dechiffrer_nombre_jouet(chiffrer_nombre_jouet(valeur)) == valeur
assert echange_jouet('', 7)[1:] == (b'', '')
for cle in (True, 0, 33, 1.5):
    try:
        echange_jouet('A', cle)
    except ValueError:
        pass
    else:
        raise AssertionError('clé hors contrat acceptée')

Trace

  • 7 est transporté sous la forme du nombre 13, puis retrouvé avec l’autre exposant.
  • Le texte UTF-8 devient une suite d’octets, tous compris entre 0 et 255.
  • Un octet modifié donne un autre texte sans alerte : l’intégrité n’est pas assurée.

Clinique de bogue

Indice observéLe contrôle fictif accepte ple.invalid alors que le seul nom présenté est exemple.invalid.

CauseSi noms est une chaîne au lieu d’une liste, in recherche une sous-chaîne et accepte un nom partiel. La correction impose une liste de noms complets, compare les noms ASCII sans tenir compte de la casse et exige des résultats booléens explicites. Elle ne calcule toujours aucune signature réelle.

Poursuivre avec l’abonnement

Passer de la lecture à la pratique

Lis les six explications, les six schémas et les exemples résolus, puis essaie les modèles Python.

Entraîne-toi avec six ateliers, douze questions corrigées, douze cartes et un projet de diagnostic à adapter.

12 questions · 12 cartes. Ta reprise et tes révisions sont enregistrées dans ce navigateur. Elles ne se synchronisent pas entre appareils.

Accéder à l’entraînement

Vérifier et prolonger

Sources du cours

Édition Maxdecours · Vérifié le .

Spécialité NSI, Terminale générale · programme du BO spécial du 25 juillet 2019

  1. Programme NSI de Terminale généraleMinistère de l’Éducation nationale · consulté le 2026-09-06
  2. Des circuits aux systèmes sur pucesÉduscol · consulté le 2026-09-06
  3. Protocoles de routage RIP et OSPFÉduscol · consulté le 2026-09-06
  4. The Abstraction: The ProcessRemzi et Andrea Arpaci-Dusseau, OSTEP · consulté le 2026-09-06
  5. Scheduling: IntroductionRemzi et Andrea Arpaci-Dusseau, OSTEP · consulté le 2026-09-06
  6. Common Concurrency ProblemsRemzi et Andrea Arpaci-Dusseau, OSTEP · consulté le 2026-09-06
  7. ps : instantané des processusProjet procps-ng, manuel · consulté le 2026-09-06
  8. RIP Version 2, RFC 2453IETF, RFC Editor · consulté le 2026-09-06
  9. OSPF Version 2, RFC 2328IETF, RFC Editor · consulté le 2026-09-06
  10. TLS 1.3, RFC 8446IETF, RFC Editor · consulté le 2026-09-06
  11. Service Identity in TLS, RFC 9525IETF, RFC Editor · consulté le 2026-09-06
  12. Types texte et octets de PythonPython Software Foundation · consulté le 2026-09-06
  13. Noms de domaine réservés, RFC 2606IETF, RFC Editor · consulté le 2026-09-06