Maskers en controleframes · oefening
Gemaskeerde frames en de richtingsregel
apply_mask maakt van een payload gemaskeerde bytes en weer terug, maar nog geen enkel 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 deis 1, tekst. 85: byte 1.0x85is0x80 | 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 leest | Verwacht | Roept aan |
|---|---|---|
| een server, die de frames van een client leest | MASK gezet | decode_frame(read_exact, require_mask=True) |
| een client, die de frames van een server leest | MASK uit | decode_frame(read_exact), waarbij require_mask standaard False is |
decode_frame vergelijkt dus de MASK-bit van het frame met require_mask en werpt 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 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:
read_exact(2), gelezen doorparse_header_bytes;de 0, 2 of 8 uitgebreide lengtebytes, gelezen door
read_length;als MASK gezet is,
read_exact(4), de sleutel;read_exact(length), de payload, gedemaskeerd metapply_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 NotImplementedError opwerpt:
encode_frame(opcode, payload, *, fin=True, mask_key=None): geef metmask_key=Nonezoals eerder het niet-gemaskeerde frame terug. Werp andersValueErrorop tenzij de sleutel precies 4 bytes is, en geef byte 0 terug, daarna de lengtebytes met0x80met OR in de eerste ervan gezet, daarna de sleutel, en daarna de payload gemaskeerd metapply_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 werpValueErrorop als de MASK-bit niet is waarrequire_maskom 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 metmask_key=Nonezoals eerder het niet-gemaskeerde. Werp anders op tenzij mask_keyprecies 4 bytes is, en geef byte 0 terug, de lengtebytes met0x80(MASK) met OR in de eerste ervan gezet, de sleutel, en de payload gemaskeerd metapply_mask.decode_frame(read_exact, *, require_mask=False, max_size=MAX_SIZE): werpValueErrorop als de MASK-bit van het frame niet is waarrequire_maskom vraagt (een server geeftTruemee, een client houdt de standaardwaardeFalse). Vergelijk de lengte metmax_sizedirect naread_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.