0%

Frames, byte voor byte · oefening

De eerste twee bytes lezen

Elk - begint met dezelfde twee bytes, en die twee bytes vertellen een lezer alles wat hij moet weten voordat de rest aankomt: of dit frame een bericht afsluit, wat voor frame het is, of de payload gemaskeerd is, en hoe lang de payload is, of waar dat te vinden is. Hier is het hele frame, byte voor byte uitgezet naar het diagram in RFC 6455, sectie 5.2, en ingevuld voor het “Hello”-frame van de RFC, 81 05 48 65 6c 6c 6f. Lees eerst de twee rijen bits: elk vakje is één bit, en de labels erboven zeggen bij welk veld de bit hoort.

Byte 0 van het Hello-frame, 0x81, bevat FIN 1, drie gereserveerde bits die nul zijn en opcode 1; byte 1, 0x05, bevat MASK 0 en de lengte 5; langere lengtebytes en een maskeersleutel verschijnen alleen in sommige frames, en de payload van 5 bytes komt als laatste.

In woorden is een frame, op volgorde:

  • byte 0: FIN, RSV1, RSV2 en RSV3, elk één bit, daarna een van 4 bits;

  • byte 1: MASK, één bit, daarna een payloadlengte van 7 bits;

  • nog 2 lengtebytes, alleen als de lengte van 7 bits 126 is, of nog 8 lengtebytes, alleen als die 127 is;

  • een van 4 bytes, alleen als MASK 1 is;

  • als laatste de payload.

In het “Hello”-frame is byte 0 0x81: FIN 1, de gereserveerde bits 0, en opcode 1, tekst. Byte 1 is 0x05: MASK 0 en lengte 5. Er volgt geen langere lengte en geen maskeersleutel, dus de vijf payloadbytes 48 65 6c 6c 6f, “Hello”, komen als volgende.

Byte 0 is de byte die je in les 1 uit elkaar haalde. Byte 1 bevat nog een vlag en een getal van 7 bits. Dat getal is een lengteprefix, hetzelfde idee als de header van 4 bytes in Networks with Python hoofdstuk 8: het vertelt de lezer hoeveel payloadbytes er volgen. Zeven bits komen echter maar tot 127, dus twee van de betekenen iets anders, en daar gaat les 4 over. De maskeersleutel hoort bij hoofdstuk 4. Deze les leest de twee bytes die elk frame heeft.

Byte 0

De hoogste bit, 0x80, is FIN, kort voor final (laatste). Een gezette FIN-bit betekent dat dit frame het laatste stuk van zijn bericht is. Een groot bericht kan als meerdere frames worden gestuurd, fragmenten genoemd, met FIN uit op alle behalve het laatste. Elk bericht in dit hoofdstuk past in één frame, dus FIN is gezet in de frames die je bouwt, behalve één keer in les 3, waar je hem uitzet om te zien dat de bit van jou is; hoofdstuk 4 stuurt hele berichten in fragmenten.

De volgende drie bits, 0x40, 0x20 en 0x10, zijn RSV1, RSV2 en RSV3, de gereserveerde bits. Ze zijn vrij voor extensies waar beide kanten het tijdens de over eens worden; compressie gebruikt bijvoorbeeld RSV1. Deze cursus spreekt geen enkele extensie af, dus alle drie moeten 0 zijn, en een frame waarin er een gezet is, wordt geweigerd. (b0 >> 4) & 0x07 schuift ze naar onderen en houdt alleen die drie, wat een getal van 0 tot en met 7 geeft dat 0 moet zijn.

De laagste vier bits, b0 & 0x0F, zijn de opcode: het soort frame. RFC 6455 definieert zes van de zestien mogelijke waarden:

opcodeframe
0x0vervolgframe, een later stuk van een gefragmenteerd bericht
0x1tekst, een bericht in
0x2binair, een bericht van willekeurige bytes
0x3 tot en met 0x7gereserveerd
0x8close
0x9
0xApong
0xB tot en met 0xFgereserveerd

De opcodes vanaf 0x8 zijn controleframes: ze beheren de verbinding in plaats van een bericht te dragen, en hoofdstuk 4 gaat over ze. Gereserveerde opcodes zijn opzijgezet voor toekomstige versies van het protocol. Geen enkele peer mag er vandaag een sturen, dus een lezer weigert elk frame dat er een gebruikt. ws_frames.py somt de zes gedefinieerde opcodes al op in KNOWN_OPCODES.

Een frame begint met de byte 0x89. Wat is het?

Waarom moet parse_header_bytes een frame weigeren waarvan de eerste byte 0x83 is?

Byte 1

De hoogste bit van byte 1, 0x80, is MASK. Als die gezet is, volgt er na de lengte een maskeersleutel van 4 bytes, en is de payload ermee door elkaar gehusseld. Clients maskeren elk frame dat ze sturen en servers doen dat nooit; hoofdstuk 4 legt uit waarom. Voorlopig meldt je parser alleen de vlag.

De andere zeven bits, b1 & 0x7F, zijn de payloadlengte. Het masker 0x7F is 01111111: het houdt zeven bits en wist MASK, dus een gemaskeerd frame van 5 bytes, 0x85, leest nog steeds als lengte 5. Zoals les 1 liet zien, bindt & sterker dan !=, dus b1 & 0x80 != 0 is al de vergelijking (b1 & 0x80) != 0, en die is True of False.

Wat drukt dit af?

b1 = 0x85
print(b1 & 0x80 != 0, b1 & 0x7F)

Je neemt de twee bytes aan als getallen. Een frame komt aan als bytes-waarde, en indexeren in een bytes-waarde geeft het getal in die byte, dus voor een frame frame is frame[0] b0 en frame[1] b1. Hier is een klein binair frame met een payload van 3 bytes, zo gelezen:

Try it

not bindt losser dan &, dus not b1 & 0x80 vraagt of de MASK-bit uit staat. Het programma van dit hoofdstuk roept je functie op dezelfde manier aan, op het “Hello”-frame van de RFC.

Je opdracht

Schrijf in ws_frames.py parse_header_bytes(b0, b1). Geef een terug met vijf sleutels:

  • "fin": True als de hoogste bit van b0 gezet is, anders False;

  • "rsv": de drie gereserveerde bits als getal, (b0 >> 4) & 0x07;

  • "opcode": de laagste vier bits van b0;

  • "masked": True als de hoogste bit van b1 gezet is, anders False;

  • "length": de laagste zeven bits van b1.

Werp op als een gereserveerde bit gezet is, of als de opcode niet in KNOWN_OPCODES staat; een die de foute waarde noemt, maakt de melding nuttig. Maak van "fin" en "masked" echte True- of False-waarden, geen 128 of 0: vergelijk met 0 zoals hierboven.

Druk op Run. De sectie van les 2 leest nu de eerste twee bytes van het tekstframe van de RFC voor “Hello”:

Lesson 2: the first two bytes of Hello
{'fin': True, 'rsv': 0, 'opcode': 1, 'masked': False, 'length': 5}

Een laatste tekstframe, niet gemaskeerd, met nog 5 bytes payload te gaan. Druk daarna op Submit. De controle roept je functie aan op frameheaders die je nog niet hebt gezien, waaronder een gemaskeerde, een die niet de laatste is, en elke gereserveerde bit en verscheidene gereserveerde opcodes apart.

Je kunt nu de header van een frame lezen. De volgende les schrijft er een: encode_frame bouwt het “Hello”-frame waar deze twee bytes vandaan kwamen.

Opdracht

Schrijf parse_header_bytes(b0, b1) in ws_frames.py. Het neemt de eerste twee bytes van een aan als getallen en geeft een terug:

  • "fin": True als de hoogste bit van b0 (0x80) gezet is, anders False;

  • "rsv": de drie gereserveerde bits als getal, (b0 >> 4) & 0x07;

  • "opcode": de laagste vier bits van b0;

  • "masked": True als de hoogste bit van b1 gezet is, anders False;

  • "length": de laagste zeven bits van b1.

Werp op als een gereserveerde bit gezet is, en als de niet in KNOWN_OPCODES staat.

Druk op Run om de sectie van les 2 te zien, en druk daarna op Submit. De controle roept parse_header_bytes aan met headerbytes die je nog niet hebt gezien: frames die wel en niet de laatste zijn, gemaskeerd en niet gemaskeerd, met de lengtes 0, 125, 126 en 127, elke gereserveerde bit apart gezet, en verscheidene gereserveerde opcodes. "fin" en "masked" moeten True of False zijn. Je dict mag extra sleutels bevatten. Laat de andere in ws_frames.py zoals ze zijn.