0%

Hoofdstuk 3 · oefening

Frames, byte voor byte

Bits binnen een byte

Zodra de server 101 heeft geantwoord, is de voorbij en is de verbinding weer een gewone stroom bytes. Vanaf hier sturen beide kanten : kleine pakketjes bytes, elk met een header die zegt wat voor bericht het draagt en hoe lang het is. RFC 6455 beschrijft die header in sectie 5.2. De eerste byte ervan, hier getekend naar het diagram van de RFC, bevat al vijf aparte velden:

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 . Acht bits, vijf velden. Indexeren in een bytes- geeft de hele byte als één getal van 0 tot en met 255, en niets tot nu toe haalt dat getal uit elkaar. Deze les voegt een manier toe om bits te schrijven en vijf die erop werken.

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:

Try it

De laatste regel toont een die je in elke les van dit hoofdstuk tegenkomt: 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 , pikt het de bits eruit die je wilt en wist het de rest. 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.

Try it

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 die, les voor les, berichten in frames zal omzetten en frames terug in berichten. De docstring van elke noemt de les die haar schrijft. De boom bevat ook 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 van n (0 tot en met 255) terug als tekst, hoogste bit eerst, dus bit_string(0x81) is "10000001". Bouw hem binnen bit_string zelf met een over 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 geen bin() 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. Werp op tenzij beide helften 0 tot en met 15 zijn.

  • flip_bits(n, mask) geeft n terug met elke bit omgedraaid die in mask gezet 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 van n (0 tot en met 255) als tekst, hoogste bit eerst, voorloopnullen inbegrepen. Schrijf de binnen bit_string zelf, over de posities 7 tot en met 0, en neem elke bit met >> en &; gebruik geen bin() of formaatcode.

  • nibbles(n): {"high": ..., "low": ...}, de hoogste vier bits en de laagste vier bits van n, elk als getal van 0 tot en met 15.

  • join_nibbles(high, low): de byte waarvan de hoogste vier bits high zijn en de laagste vier bits low. Werp op als een van beide buiten 0 tot en met 15 valt.

  • flip_bits(n, mask): n met elke bit omgedraaid die in mask gezet 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 . Ze leest ook de code van bit_string op >> of & en op een aanroep van bin() of een formaatcode. Laat de andere in ws_frames.py zoals ze zijn.