0%

Frames, byte voor byte · oefening

Drie manieren om te zeggen hoe lang

Zeven bits bevatten de getallen 0 tot en met 127, en een payload kan veel langer zijn dan dat. RFC 6455 lost dit op zonder elke header groter te maken. Lengtes van 0 tot en met 125 gaan rechtstreeks in het veld van 7 bits, zoals in les 3. De twee overgebleven zijn helemaal geen lengtes maar markeringen: 126 betekent “de echte lengte volgt in de volgende 2 bytes”, en 127 betekent “de echte lengte volgt in de volgende 8 bytes”. Eén klein veld kiest tussen drie indelingen.

Hier zijn de headers op de grenzen, elke byte vóór de payload. De eerste vier zijn tekstframes en de rest binair, wat alleen de eerste byte verandert:

payload bytes   header
0               81 00
125             81 7d
126             81 7e 00 7e
127             81 7e 00 7f
256             82 7e 01 00
65535           82 7e ff ff
65536           82 7f 00 00 00 00 00 01 00 00

Lees de rij voor 256 bytes: 7e is 126, de markering, en 01 00 is de lengte in twee bytes, 1 keer 256 plus 0. De rij voor 65536 bytes heeft de markering 127, 7f, en daarna acht bytes die 65536 bevatten, wat 1 keer 256 keer 256 is. De RFC zelf gebruikt die twee rijen als voorbeelden van de langere vormen.

struct, opnieuw

Twee bytes en acht bytes zijn klussen voor struct, dat je in Networks with Python hoofdstuk 8 gebruikte met het formaat "!I", één unsigned geheel getal van 4 bytes in . Deze les heeft twee andere groottes nodig. "!H" is één unsigned geheel getal in 2 bytes, 0 tot en met 65535, en "!Q" is er een in 8 bytes. De ! betekent nog steeds netwerkbytevolgorde, big-endian, meest significante byte eerst, en de RFC eist precies die volgorde. struct.unpack geeft nog steeds een terug, dus het getal is item [0]:

Try it

Wat drukt dit af?

import struct

print((bytes([0x81, 126]) + struct.pack("!H", 127)).hex(" "))

Altijd de kortste vorm

Niets in de bytes houdt een afzender tegen om de lengte 5 te schrijven als de markering 126 en daarna 00 05. RFC 6455 sluit het uit: sectie 5.2 zegt dat het minimale aantal bytes moet worden gebruikt om de lengte te coderen. Elke lengte heeft dus precies één header, en een lezer weigert elke andere. Deze drie worden geweigerd:

81 7e 00 7d                     a 16-bit length of 125, which fits in 7 bits
82 7f 00 00 00 00 00 00 ff ff   a 64-bit length of 65535, which fits in 16 bits
82 7f 80 00 00 00 00 00 00 00   a 64-bit length with its top bit set

De derde regel staat los van de rest. De RFC eist dat de hoogste bit van de lengte van 8 bytes 0 is, dus de grootste lengte die een kan aankondigen is 2 tot de macht 63, min 1. Dat is nog steeds veel meer dan een server zou moeten accepteren, en een lezer die het geloofde, zou wachten op, of proberen vast te houden, een payload die geen enkel programma aankan. Les 5 voegt om die reden een groottelimiet toe.

Een frame zegt lengte 126 en daarna 00 7d. Wat moet een decoder doen?

De functie read_length

Coderen is één van één getal. Decoderen heeft een complicatie: na de lengte van 7 bits moet de lezer misschien nog 0, 2 of 8 bytes lezen, en hij kan pas weten hoeveel als hij byte 1 heeft gelezen. Daarom neemt read_length(length, read_exact) twee aan: de waarde van 7 bits uit parse_header_bytes en een functie om meer bytes mee te lezen.

read_exact(n) is de functie die je in Networks with Python hoofdstuk 6, les 3, schreef als read_exact(reader, n): hij geeft precies n bytes terug, hoe de verbinding ze ook aflevert, houdt extra bytes in een buffer, en werpt ConnectionError op als de peer eerst sluit. Hier komt hij binnen als , een functie die als waarde wordt meegegeven, zodat read_length nooit hoeft te weten waar de bytes vandaan komen. Een echte verbinding, een test en frames_demo.py kunnen hem elk hun eigen read_exact geven. Bijvoorbeeld met een stand-in uit fakes.py van het hoofdstuk die de bytes 01 00 bevat:

read_exact = fakes.reader_over([bytes([1, 0])])
# reads 2 bytes and returns 256
ws_frames.read_length(126, read_exact)

Voor een lengte van 125 of minder leest read_length niets en geeft het de lengte zelf terug. Voor 126 leest het 2 bytes en pakt die uit met "!H"; voor 127 8 bytes met "!Q". Daarna controleert het de regels hierboven voordat het iets teruggeeft.

Je opdracht

In ws_frames.py:

  1. Schrijf length_field(n). Geef bytes([n]) terug voor 0 tot en met 125; de byte 126 gevolgd door struct.pack("!H", n) voor 126 tot en met 65535; en de byte 127 gevolgd door struct.pack("!Q", n) daarboven.

  2. Schrijf read_length(length, read_exact). Geef length terug als die 125 of minder is, zonder te lezen. Lees na 126 2 bytes en werp op als het getal 125 of minder is. Lees na 127 8 bytes en werp ValueError op als de hoogste bit gezet is (het getal is groter dan 0x7FFFFFFFFFFFFFFF) of als het getal 65535 of minder is.

  3. Pas encode_frame aan: laat de limiet van 125 bytes vallen en bouw de lengtebytes met length_field(len(payload)). Al het andere blijft zoals les 3 het schreef.

Druk op Run. De sectie van les 4 drukt geen weigering meer af. Ze toont de eerste tien bytes van een binair frame van 65536 bytes, de header van de RFC:

Lesson 4: the start of a 65536-byte frame
82 7f 00 00 00 00 00 01 00 00

Druk daarna op Submit. De controle vergelijkt length_field met bytes die met struct zijn gebouwd op elke grens en bij lengtes die je nog niet hebt gezien, controleert de headers van 256 bytes en 65536 bytes van de RFC uit encode_frame samen met de kleine frames van les 3, en geeft read_length een stand-in-read_exact die noteert om hoeveel bytes er gevraagd werd. Om meer vragen dan de vorm nodig heeft, faalt, en een van de drie geweigerde headers accepteren ook.

De codec kan nu elke lengte schrijven en lezen. De volgende les zet de leesonderdelen samen in decode_frame, dat één heel frame uit een stroom leest.

Opdracht

In ws_frames.py:

  1. Schrijf length_field(n): de lengtebytes voor een payload van n bytes in de kortste vorm. bytes([n]) voor 0 tot en met 125; de byte 126 en daarna struct.pack("!H", n) voor 126 tot en met 65535; de byte 127 en daarna struct.pack("!Q", n) daarboven.

  2. Schrijf read_length(length, read_exact): geef length zelf terug als die 125 of minder is, zonder iets te lezen. Als die 126 is, lees je 2 bytes met read_exact(2) en pak je ze uit met "!H"; als die 127 is, lees je 8 bytes en pak je ze uit met "!Q". Werp op voor een lengte die een kortere vorm had moeten gebruiken (125 of minder na 126, 65535 of minder na 127) en voor een lengte van 8 bytes met de hoogste bit gezet.

  3. Pas encode_frame zo aan dat het geen limiet van 125 bytes heeft en zijn lengtebytes bouwt met length_field(len(payload)).

Druk op Run: de sectie van les 4 moet 82 7f 00 00 00 00 00 01 00 00 afdrukken. Druk daarna op Submit. De controle vergelijkt length_field met bytes die ze met struct bouwt, op elke grens en bij lengtes die je nog niet hebt gezien; controleert de headers van 256 bytes en 65536 bytes van de RFC en de kleine van les 3 uit encode_frame; en geeft read_length een stand-in-read_exact die elk aantal noteert waarom gevraagd wordt. read_length moet om niets, precies 2 of precies 8 bytes vragen, en moet een 16-bits 125 of 0, een 64-bits 65535, en een 64-bits lengte met de hoogste bit gezet weigeren. int.to_bytes en int.from_bytes met "big" zijn ook goed in plaats van struct.