Hoofdstuk 3 · oefening
Frames, byte voor byte
Bits binnen een byte
Zodra de server 101 heeft geantwoord, is de
the first byte of every frame
+-------+-------+-------+-------+-----------------------+
| FIN | RSV1 | RSV2 | RSV3 | opcode (4 bits) |
+-------+-------+-------+-------+-----------------------+
1 bit 1 bit 1 bit 1 bit
In woorden: de meest linkse bit is een vlag die FIN heet, de volgende drie bits zijn nog drie vlaggen, RSV1, RSV2 en RSV3, en de laatste vier bits vormen samen één klein getal, de bytes-
Bits schrijven
Python schrijft één geheel getal op drie manieren. 129 is decimaal, zoals gewoonlijk. 0b10000001 is binair: het voorvoegsel 0b zegt dat elk cijfer erna een bit is, van links naar rechts 128, 64, 32, 16, 8, 4, 2 en 1 waard. 0x81 is hexadecimaal, of hex: het voorvoegsel 0x zegt dat elk cijfer erna een van zestien waarden is, geschreven als 0 tot en met 9 en daarna a tot en met f voor 10 tot en met 15. Protocollen houden van hex omdat elk hexcijfer precies vier bits is, dus twee cijfers vormen één byte. In 0x81 is de 8 de bits 1000 en de 1 de bits 0001, naast elkaar.
Underscores mogen cijfers scheiden voor de leesbaarheid: 0b1000_0001 is hetzelfde getal. int leest binaire tekst als je het grondtal 2 meegeeft, en hex() schrijft een getal in hex:
De laatste regel toont een hex(" ") op een bytes-waarde schrijft elke byte als twee hexcijfers, gescheiden door spaties. Protocoldocumenten, RFC 6455 inbegrepen, schrijven bytes in hex, en dit hoofdstuk schrijft elk frame zo; het programma ervan drukt die van jou op dezelfde manier af.
Testen en uitlichten met &
a & b houdt een bit alleen waar beide getallen hem gezet hebben. Gebruikt met een vast getal, een n & 0x80 houdt alleen de hoogste bit van een byte, dus het is 128 als die bit gezet is en 0 als hij dat niet is. n & 0x0F houdt de laagste vier bits, en daar zit precies de opcode.
Om te vragen of een vlag gezet is, vergelijk je het resultaat met 0. In Python bindt & sterker dan == en !=, dus n & 0x80 != 0 betekent (n & 0x80) != 0.
Wat drukt dit af?
print(0x85 & 0x80, 0x85 & 0x7F)
Verschuiven met << en >>
n >> k schuift de bits van n k plaatsen naar rechts. De k laagste bits vallen eraf, dus 0x81 >> 4 laat de laagste vier bits 0001 vallen en houdt 1000 over, wat 8 is. n << k schuift naar links en vult aan met nullen, dus 1 << 7 is 128, één bit op de hoogste positie.
Samen lezen een verschuiving en een masker één bit op elke positie: (n >> 3) & 1 schuift bit 3 naar onderen en houdt alleen die, wat 0 of 1 geeft. Zo geteld, van 0 rechts tot 7 links, is positie 7 de hoogste bit.
Wat is 0xC1 >> 4?
Combineren met | en omdraaien met ^
a | b zet elke bit die in een van beide getallen gezet is. Het combineert velden die elkaar niet overlappen: de vlag 0x80 en de opcode 0x01 maken 0x80 | 0x01, en dat is 0x81.
a ^ b is exclusieve of. Elke bit die in b gezet is, wordt in a omgedraaid: een 1 wordt een 0 en een 0 wordt een 1. Dezelfde bits twee keer omdraaien geeft terug waarmee je begon, dus x ^ k ^ k is altijd x. Hoofdstuk 4 rekent precies daarop.
De tweede regel draait één bit van 0x81 om, daarna alle acht.
Wat drukt dit af?
print((0x48 ^ 0x37) ^ 0x37)
Lees 0x81, wat 10000001 is in bits, als een hoogste bit en een laag getal van vier bits.
Je opdracht
Open ws_frames.py. Het is de leercodec van het hoofdstuk: een ws_handshake.py, de afgewerkte versie van de cursus van de handshake uit hoofdstuk 2, zodat dit hoofdstuk begint vanuit een bekend bestand, hoe je eigen code uit hoofdstuk 2 er ook uitzag. Niets hier past het aan. Schrijf de vier hulpfuncties van les 1:
bit_string(n)geeft de 8 bits vann(0 tot en met 255) terug als tekst, hoogste bit eerst, dusbit_string(0x81)is"10000001". Bouw hem binnenbit_stringzelf met eenover de posities 7 tot en met 0 ( range(7, -1, -1)), waarbij je elke bit neemt met>>en&en"0"of"1"aan de tekst toevoegt. Gebruik geenbin()of formaatcode: de lus schrijven is de oefening.nibbles(n)geeft de hoogste en laagste vier bits terug als{"high": ..., "low": ...}. Vier bits heten vaak een nibble, een halve byte.join_nibbles(high, low)doet het omgekeerde en geeft de byte terug. Werpop tenzij beide helften 0 tot en met 15 zijn. flip_bits(n, mask)geeftnterug met elke bit omgedraaid die inmaskgezet is.
Druk op Run. Run start in dit hoofdstuk frames_demo.py, een kort programma dat voor de cursus is geschreven en per les één sectie afdrukt op de eigen voorbeeldbytes van de RFC; een functie die nog een stub is, drukt not written yet af. Lees het: de laatste twee regels roepen show_lessons() alleen aan als het bestand als programma wordt gestart, de bewaker if __name__ == "__main__":. Met je hulpfuncties geschreven begint de uitvoer zo:
Lesson 1: 0x81 in bits
{'bits': '10000001', 'nibbles': {'high': 8, 'low': 1}}
Lesson 2: the first two bytes of Hello
not written yet
Druk daarna op Submit. De controle roept elke hulpfunctie aan op bytes die je nog niet hebt gezien en leest de code van bit_string op >> en & en op de kortere wegen die de opdracht uitsluit. In de volgende les lezen deze operatoren de eerste twee bytes van een echt frame.
Opdracht
Schrijf de vier hulpfuncties van les 1 in ws_frames.py:
bit_string(n): de 8 bits vann(0 tot en met 255) als tekst, hoogste bit eerst, voorloopnullen inbegrepen. Schrijf debinnen bit_stringzelf, over de posities 7 tot en met 0, en neem elke bit met>>en&; gebruik geenbin()of formaatcode.nibbles(n):{"high": ..., "low": ...}, de hoogste vier bits en de laagste vier bits vann, elk als getal van 0 tot en met 15.join_nibbles(high, low): de byte waarvan de hoogste vier bitshighzijn en de laagste vier bitslow. Werpop als een van beide buiten 0 tot en met 15 valt. flip_bits(n, mask):nmet elke bit omgedraaid die inmaskgezet is.
Druk op Run om de sectie van les 1 in frames_demo.py te zien, en druk daarna op Submit. De controle roept elke hulpfunctie aan op bytes die je nog niet hebt gezien, met helften buiten 0 tot en met 15 voor join_nibbles, en draait één byte twee keer om met hetzelfde bit_string op >> of & en op een aanroep van bin() of een formaatcode. Laat de andere ws_frames.py zoals ze zijn.