Kapitel 3 · Übung
Frames, Byte für Byte
Bits in einem Byte
Sobald der Server mit 101 geantwortet hat, ist der
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 bytes-
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:
Die letzte Zeile zeigt eine 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 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.
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 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 vonn(0 bis 255) als Text zurück, das höchste Bit zuerst, also istbit_string(0x81)gleich"10000001". Baue ihn inbit_stringselbst 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 wederbin()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öseaus, außer wenn beide Hälften zwischen 0 und 15 liegen. flip_bits(n, mask)gibtnzurück, wobei jedes Bit umgekehrt ist, das inmaskgesetzt 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 vonn(0 bis 255) als Text, das höchste Bit zuerst, führende Nullen eingeschlossen. Schreibe diein bit_stringselbst, über die Positionen 7 bis hinunter zu 0, und nimm jedes Bit mit>>und&; verwende wederbin()noch einen Formatcode.nibbles(n):{"high": ..., "low": ...}, die oberen vier Bits und die unteren vier Bits vonn, jeweils als Zahl von 0 bis 15.join_nibbles(high, low): das Byte, dessen obere vier Bitshighund dessen untere vier Bitslowsind. Löseaus, wenn eines davon außerhalb von 0 bis 15 liegt. flip_bits(n, mask):n, wobei jedes Bit umgekehrt ist, das inmaskgesetzt 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 bit_string auf >> oder & und auf einen Aufruf von bin() oder einen Formatcode. Lass die anderen ws_frames.py, wie sie sind.