0%

Maskers en controleframes · oefening

Gemaskeerde frames en de richtingsregel

apply_mask maakt van een payload gemaskeerde bytes en weer terug, maar nog geen enkel draagt een : encode_frame werpt NotImplementedError op zodra het een sleutel krijgt, en decode_frame doet hetzelfde als het gevraagd wordt te lezen als server. Deze les vervangt beide takken. Hier is het gemaskeerde “Hello” van de RFC nog eens, veld voor veld:

81 85 37 fa 21 3d 7f 9f 4d 51 58
  • 81: byte 0, niet veranderd door maskeren. FIN is gezet en de is 1, tekst.

  • 85: byte 1. 0x85 is 0x80 | 5, de MASK-bit bovenop de lengte van 7 bits, 5.

  • 37 fa 21 3d: de maskeersleutel, alleen aanwezig omdat MASK gezet is.

  • 7f 9f 4d 51 58: de payload, “Hello” gemaskeerd met die sleutel.

Alleen de payload wordt gemaskeerd. De header en de lengte blijven leesbaar, omdat de ontvanger ze nodig heeft om te weten hoeveel bytes hij moet lezen en of er een sleutel volgt.

MASK zit in byte 1

De MASK-bit is de hoogste bit van byte 1, dezelfde byte die de lengte van 7 bits bevat. length_field(n) uit hoofdstuk 3 geeft de lengtebytes in de kortste vorm terug, en de eerste byte daarvan is altijd dat veld van 7 bits: de lengte zelf tot 125, of de markering 126 of 127. MASK zetten betekent 0x80 met OR in die eerste byte zetten en elke uitgebreide lengtebyte erna ongemoeid laten. Daarna komen de vier sleutelbytes, en daarna de gemaskeerde payload. Met de sleutel 01 02 03 04 zijn dit de headers van een gemaskeerd frame van 256 bytes, een gemaskeerd frame van 65.536 bytes, en een leeg gemaskeerd tekstframe:

82 fe 01 00 01 02 03 04
82 ff 00 00 00 00 00 01 00 00 01 02 03 04
81 80 01 02 03 04

In het eerste is fe 0x80 | 126, en de lengte van 16 bits 01 00 volgt zoals eerder. In het tweede is ff 0x80 | 127, en volgen er acht lengtebytes. Het lege frame draagt toch een sleutel, ook al is er niets te maskeren.

Wat drukt dit af?

print(hex(0x80 | 126), hex(0x80 | 5))

Waar sleutels vandaan komen

RFC 6455 sectie 5.3 vraagt de client om een nieuwe sleutel voor elk frame, zo gekozen dat niemand hem kan voorspellen. In hoofdstuk 5 haalt je client hem uit os.urandom(4), de bron van onvoorspelbare bytes van de standaardbibliotheek. encode_frame roept die niet aan. Het neemt de sleutel aan als het mask_key, op dezelfde manier als make_client_key(random_bytes=os.urandom) in hoofdstuk 2 zijn bron van willekeurige bytes als argument aannam. Een test kan dan een bekende sleutel meegeven en het frame vergelijken met bekende bytes, zoals frames_demo.py doet met de sleutel van de RFC. Als mask_key None is, de standaardwaarde, bouwt encode_frame precies zoals eerder een niet-gemaskeerd frame, en dat is wat een server stuurt.

De richtingsregel

RFC 6455 sectie 5.1 noemt de regel in beide richtingen. Een client maskeert elk frame dat hij stuurt, en een server maskeert er geen. Een server die een niet-gemaskeerd frame ontvangt, moet de verbinding sluiten, en een client die een gemaskeerd frame ontvangt, moet hetzelfde doen. Elke kant leest frames van de andere, dus elke kant weet welke soort ze kan verwachten:

Wie leestVerwachtRoept aan
een server, die de frames van een client leestMASK gezetdecode_frame(read_exact, require_mask=True)
een client, die de frames van een server leestMASK uitdecode_frame(read_exact), waarbij require_mask standaard False is

decode_frame vergelijkt dus de MASK-bit van het frame met require_mask en werpt op als ze verschillen, in beide richtingen. De standaardwaarde False houdt het gedrag van hoofdstuk 3, het gezichtspunt van de client, en een server die require_mask=True vergeet, weigert elk frame dat zijn client stuurt: een luide fout, gevonden bij het eerste bericht. De RFC zegt dat de ontvanger de verbinding sluit. Welke hij stuurt, is een vraag voor les 4 en 6; voorlopig is de weigering een ValueError.

Je server leest een frame waarvan de MASK-bit 0 is. Wat doet hij?

Een gemaskeerd frame decoderen

Een gemaskeerd frame wordt gelezen in de volgorde waarin de header het beschrijft, één stap meer dan in hoofdstuk 3:

  1. read_exact(2), gelezen door parse_header_bytes;

  2. de 0, 2 of 8 uitgebreide lengtebytes, gelezen door read_length;

  3. als MASK gezet is, read_exact(4), de sleutel;

  4. read_exact(length), de payload, gedemaskeerd met apply_mask(payload, key).

De sleutel komt na de uitgebreide lengte, dus als je hem eerder las, zou je lengtebytes voor de sleutel nemen. De groottecontrole blijft waar ze was, direct na read_length: een frame dat meer dan max_size bytes aankondigt, wordt geweigerd voordat de sleutel en de payload gelezen worden.

decode_frame heeft 82 fe 01 00 gelezen. Wat leest het daarna?

Je opdracht

Vervang in ws_frames.py de tak bovenaan elke die NotImplementedError opwerpt:

  • encode_frame(opcode, payload, *, fin=True, mask_key=None): geef met mask_key=None zoals eerder het niet-gemaskeerde frame terug. Werp anders ValueError op tenzij de sleutel precies 4 bytes is, en geef byte 0 terug, daarna de lengtebytes met 0x80 met OR in de eerste ervan gezet, daarna de sleutel, en daarna de payload gemaskeerd met apply_mask.

  • decode_frame(read_exact, *, require_mask=False, max_size=MAX_SIZE): vervang de weigering van elk gemaskeerd frame uit hoofdstuk 3 door de richtingsregel, en werp ValueError op als de MASK-bit niet is waar require_mask om vraagt. Lees bij een gemaskeerd frame na de lengtecontrole de sleutel van 4 bytes en geef de payload gedemaskeerd terug.

Druk op Run. De twee secties van les 2 bouwen het gemaskeerde frame van de RFC en lezen het terug zoals een server dat doet, van een verbinding die één byte per keer aflevert:

Lesson 2: a masked text frame
81 85 37 fa 21 3d 7f 9f 4d 51 58
Lesson 2: the server unmasks it, one byte at a time
{'fin': True, 'opcode': 1, 'payload': b'Hello'}
Lesson 3: the client's pong for the server's ping
  not written yet

Druk daarna op Submit. De controle bouwt gemaskeerde frames met sleutels en payloads die je nog niet hebt gezien, van 0, 7, 256 en 65.536 bytes, en vergelijkt ze byte voor byte, header eerst. Ze leest gemaskeerde frames als server, in één keer afgeleverd, één byte en twee bytes per keer, en controleert de volgorde van het lezen; ze verwacht ValueError voor een sleutel die geen 4 bytes is, voor elke manier om de richtingsregel te breken, en voor een gemaskeerd frame groter dan max_size, voordat de sleutel gelezen wordt. De niet-gemaskeerde frames uit hoofdstuk 3 moeten nog steeds werken. Elk frame tot nu toe is data; de volgende les voegt de frames toe die een verbinding op orde houden.

Opdracht

Vervang in ws_frames.py de tak die NotImplementedError opwerpt bovenaan encode_frame en decode_frame:

  • encode_frame(opcode, payload, *, fin=True, mask_key=None): bouw met mask_key=None zoals eerder het niet-gemaskeerde . Werp anders op tenzij mask_key precies 4 bytes is, en geef byte 0 terug, de lengtebytes met 0x80 (MASK) met OR in de eerste ervan gezet, de sleutel, en de payload gemaskeerd met apply_mask.

  • decode_frame(read_exact, *, require_mask=False, max_size=MAX_SIZE): werp ValueError op als de MASK-bit van het frame niet is waar require_mask om vraagt (een server geeft True mee, een client houdt de standaardwaarde False). Vergelijk de lengte met max_size direct na read_length, zoals eerder; lees daarna, bij een gemaskeerd frame, de sleutel van 4 bytes en geef de payload gedemaskeerd terug.

Druk op Run: de secties van les 2 moeten 81 85 37 fa 21 3d 7f 9f 4d 51 58 en {'fin': True, 'opcode': 1, 'payload': b'Hello'} afdrukken. Druk daarna op Submit. De controle vergelijkt gemaskeerde frames van 0, 7, 256 en 65.536 bytes met onbekende sleutels byte voor byte, leest gemaskeerde frames als server in één keer, één byte en twee bytes per keer, en verwacht het lezen in de volgorde header, lengte, sleutel, payload (sleutel en payload in één keer lezen is goed). Ze verwacht ValueError voor een sleutel die geen 4 bytes is, een niet-gemaskeerd frame gelezen met require_mask=True, een gemaskeerd frame gelezen zonder, en een gemaskeerd frame groter dan max_size, zonder dat de sleutel gelezen wordt. Niet-gemaskeerde frames moeten nog steeds coderen en decoderen zoals in hoofdstuk 3.