0%

Kapitel 3 · Übung

Frames, Byte für Byte

Bits in einem Byte

Sobald der Server mit 101 geantwortet hat, ist der vorbei, und die Verbindung ist wieder ein einfacher Byte-Stream. Von hier an senden beide Seiten : kleine Pakete aus Bytes, jedes mit einem Header, der sagt, welche Art von Nachricht es trägt und wie lang es ist. RFC 6455 beschreibt diesen Header in Abschnitt 5.2. Sein erstes Byte, hier nach dem Diagramm des RFC gezeichnet, enthält bereits fünf getrennte Felder:

the first byte of every frame

+-------+-------+-------+-------+-----------------------+
|  FIN  | RSV1  | RSV2  | RSV3  |    opcode (4 bits)    |
+-------+-------+-------+-------+-----------------------+
  1 bit   1 bit   1 bit   1 bit

In Worten: Das Bit ganz links ist ein Flag namens FIN, die nächsten drei Bits sind drei weitere Flags namens RSV1, RSV2 und RSV3, und die letzten vier Bits bilden zusammen eine kleine Zahl, den . Acht Bits, fünf Felder. Das Indizieren eines bytes- liefert das ganze Byte als eine Zahl von 0 bis 255, und bisher nimmt nichts diese Zahl auseinander. Diese Lektion fügt eine Schreibweise für Bits hinzu und fünf , die mit ihnen arbeiten.

Bits schreiben

Python schreibt eine ganze Zahl auf drei Arten. 129 ist dezimal, wie gewohnt. 0b10000001 ist binär: Das Präfix 0b sagt, dass jede Ziffer danach ein Bit ist, von links nach rechts 128, 64, 32, 16, 8, 4, 2 und 1 wert. 0x81 ist hexadezimal, kurz Hex: Das Präfix 0x sagt, dass jede Ziffer danach einer von sechzehn Werten ist, geschrieben 0 bis 9 und dann a bis f für 10 bis 15. Protokolle mögen Hex, weil jede Hex-Ziffer genau vier Bits sind, also bilden zwei Ziffern ein Byte. In 0x81 ist die 8 die Bits 1000 und die 1 die Bits 0001, nebeneinander.

Unterstriche dürfen Ziffern zur besseren Lesbarkeit trennen: 0b1000_0001 ist dieselbe Zahl. int liest binären Text, wenn du ihm die Basis 2 gibst, und hex() schreibt eine Zahl in Hex:

Try it

Die letzte Zeile zeigt eine , die dir in jeder Lektion dieses Kapitels begegnet: hex(" ") auf einem bytes-Wert schreibt jedes Byte als zwei Hex-Ziffern, durch Leerzeichen getrennt. Protokolldokumente, auch RFC 6455, schreiben Bytes in Hex, und dieses Kapitel schreibt jeden Frame so; sein Programm gibt deine Frames genauso aus.

Prüfen und herausziehen mit &

a & b behält ein Bit nur dort, wo es in beiden Zahlen gesetzt ist. Mit einer festen Zahl verwendet, einer sogenannten , pickt es die Bits heraus, die du willst, und löscht den Rest. n & 0x80 behält nur das oberste Bit eines Bytes, ist also 128, wenn dieses Bit gesetzt ist, und 0, wenn nicht. n & 0x0F behält die unteren vier Bits, genau dort, wo der Opcode sitzt.

Um zu fragen, ob ein Flag gesetzt ist, vergleiche das Ergebnis mit 0. In Python bindet & stärker als == und !=, also bedeutet n & 0x80 != 0 dasselbe wie (n & 0x80) != 0.

Was gibt das aus?

print(0x85 & 0x80, 0x85 & 0x7F)

Verschieben mit << und >>

n >> k verschiebt die Bits von n um k Stellen nach rechts. Die k niedrigsten Bits fallen hinten heraus, also verwirft 0x81 >> 4 die unteren vier Bits 0001 und lässt 1000 übrig, also 8. n << k verschiebt nach links und füllt mit Nullen auf, also ist 1 << 7 gleich 128, ein einzelnes Bit an der obersten Stelle.

Zusammen lesen eine Verschiebung und eine Maske ein Bit an jeder Position: (n >> 3) & 1 schiebt Bit 3 nach ganz unten und behält nur dieses, was 0 oder 1 ergibt. So gezählt, von 0 rechts bis 7 links, ist Position 7 das oberste Bit.

Was ist 0xC1 >> 4?

Kombinieren mit | und umkehren mit ^

a | b setzt jedes Bit, das in einer der beiden Zahlen gesetzt ist. Es kombiniert Felder, die sich nicht überlappen: Das Flag 0x80 und der Opcode 0x01 ergeben 0x80 | 0x01, also 0x81.

a ^ b ist das exklusive Oder. Jedes Bit, das in b gesetzt ist, kehrt sich in a um: Aus einer 1 wird eine 0 und aus einer 0 eine 1. Dieselben Bits zweimal umzukehren ergibt wieder den Ausgangswert, also ist x ^ k ^ k immer x. Kapitel 4 verlässt sich genau darauf.

Try it

Die zweite Zeile kehrt ein Bit von 0x81 um, dann alle acht.

Was gibt das aus?

print((0x48 ^ 0x37) ^ 0x37)

Lies 0x81, in Bits 10000001, als ein oberstes Bit und eine untere 4-Bit-Zahl.

Deine Aufgabe

Öffne ws_frames.py. Das ist der Lern-Codec des Kapitels: ein , das Lektion für Lektion Nachrichten in Frames und Frames zurück in Nachrichten verwandeln wird. Der Docstring jeder nennt die Lektion, die sie schreibt. Der Dateibaum enthält außerdem ws_handshake.py, die fertige Version des Kurses für den Handshake aus Kapitel 2, sodass dieses Kapitel von einer bekannten Datei ausgeht, egal wie dein eigener Code aus Kapitel 2 aussah. Nichts hier ändert sie. Schreibe die vier Hilfsfunktionen für Lektion 1:

  • bit_string(n) gibt die 8 Bits von n (0 bis 255) als Text zurück, das höchste Bit zuerst, also ist bit_string(0x81) gleich "10000001". Baue ihn in bit_string selbst mit einer über die Positionen 7 bis hinunter zu 0 (range(7, -1, -1)), nimm jedes Bit mit >> und & und füge "0" oder "1" an den Text an. Verwende weder bin() noch einen Formatcode: Die Schleife zu schreiben ist die Übung.

  • nibbles(n) gibt die oberen und unteren vier Bits als {"high": ..., "low": ...} zurück. Vier Bits nennt man oft ein Nibble, ein halbes Byte.

  • join_nibbles(high, low) macht das Gegenteil und gibt das Byte zurück. Löse aus, außer wenn beide Hälften zwischen 0 und 15 liegen.

  • flip_bits(n, mask) gibt n zurück, wobei jedes Bit umgekehrt ist, das in mask gesetzt ist.

Klicke auf Run. Run startet in diesem Kapitel frames_demo.py, ein kurzes Programm, das für den Kurs geschrieben wurde und für jede Lektion einen Abschnitt mit den Beispielbytes des RFC ausgibt; eine Funktion, die noch ein Platzhalter ist, gibt not written yet aus. Lies es: Die letzten beiden Zeilen rufen show_lessons() nur auf, wenn die Datei als Programm gestartet wird, das ist der Wächter if __name__ == "__main__":. Mit deinen Hilfsfunktionen beginnt die Ausgabe so:

Lesson 1: 0x81 in bits
{'bits': '10000001', 'nibbles': {'high': 8, 'low': 1}}
Lesson 2: the first two bytes of Hello
  not written yet

Klicke dann auf Submit. Die Prüfung ruft jede Hilfsfunktion mit Bytes auf, die du nicht gesehen hast, und liest den Code von bit_string auf >> und & und auf die Abkürzungen, die die Aufgabe ausschließt. In der nächsten Lektion lesen diese Operatoren die ersten beiden Bytes eines echten Frames.

Aufgabe

Schreibe die vier Hilfsfunktionen für Lektion 1 in ws_frames.py:

  • bit_string(n): die 8 Bits von n (0 bis 255) als Text, das höchste Bit zuerst, führende Nullen eingeschlossen. Schreibe die in bit_string selbst, über die Positionen 7 bis hinunter zu 0, und nimm jedes Bit mit >> und &; verwende weder bin() noch einen Formatcode.

  • nibbles(n): {"high": ..., "low": ...}, die oberen vier Bits und die unteren vier Bits von n, jeweils als Zahl von 0 bis 15.

  • join_nibbles(high, low): das Byte, dessen obere vier Bits high und dessen untere vier Bits low sind. Löse aus, wenn eines davon außerhalb von 0 bis 15 liegt.

  • flip_bits(n, mask): n, wobei jedes Bit umgekehrt ist, das in mask gesetzt ist.

Klicke auf Run, um den Abschnitt zu Lektion 1 von frames_demo.py zu sehen, und klicke dann auf Submit. Die Prüfung ruft jede Hilfsfunktion mit Bytes auf, die du nicht gesehen hast, darunter Hälften außerhalb von 0 bis 15 für join_nibbles, und kehrt ein Byte zweimal mit derselben um. Sie liest außerdem den Code von bit_string auf >> oder & und auf einen Aufruf von bin() oder einen Formatcode. Lass die anderen in ws_frames.py, wie sie sind.