Frames, Byte by Byte · practice
Decode a Frame From a Stream
encode_frame turns a message into bytes. The receiving side needs the opposite: take bytes from the connection and get the message back. The problem is one you solved in Networks with Python Chapter 6. recv holds one frame works on a quiet test machine and fails on a busy network.
Networks’ answer still holds: never ask the connection “what do you have?”, ask it “give me exactly n bytes”. A frame tells you, step by step, how many bytes to ask for next.
read_exact as a parameter
decode_frame(read_exact, *, max_size=MAX_SIZE) gets its bytes only through read_exact, the same kind of read_length takes. It never sees a fakes.py. Open it and read its two functions once:
pieces(data, size)cutsdatainto aof chunks of at most sizebytes, the way a busy connection might deliver them.reader_over(chunks)returns aread_exact(n)function that takes chunks from the list in order, keeps them in a buffer until it hasnbytes, returns exactly those, and keeps the rest for the next call. When the chunks run out first, it raisesConnectionError, as a closed connection would.
So fakes.reader_over(fakes.pieces(frame, 1)) is a read_exact over a connection that delivers frame one byte at a time. That is what frames_demo.py hands your decoder in its Lesson 5 section. The one-byte pieces prove only that your decoder asks for exact counts and never assumes more has arrived; collecting the pieces into those counts is the reader’s job, the same receive buffer you wrote in Networks.
In order
A decoder reads a frame in the order its header describes it:
read_exact(2), the two bytes every frame has, whichparse_header_bytesreads;read_length(...), which reads 0, 2 or 8 more bytes depending on the 7-bit length;read_exact(length), the payload.
Each step needs the one before it to know how much to ask for, so the order is fixed. Asking for more than one step at a time, such as 14 bytes to cover the longest header, would wait forever on a 7-byte frame like the RFC’s “Hello”, or swallow the start of the next frame. Reading exactly one frame also means a second frame right behind it stays in the buffer, intact, for the next call.
decode_frame has read the first two bytes 82 7e. What does it ask for next?
Refuse before reading
max_size is the largest payload this decoder accepts, by default MAX_SIZE, one million bytes. Networks with Python Chapter 8 set the same kind of limit on its frames, for the same reason. The 64-bit length lets a peer announce a payload of billions of bytes, and a program that tried to read it would wait for bytes that may never come and hold more memory than it has.
The header already says how big the payload is. So compare the length with max_size as soon as read_length returns, and raise read_exact for a single payload byte. Checking after the payload has arrived is too late: the waiting and the memory are already spent.
Why does decode_frame compare the length with max_size before it reads the payload?
A masked frame
One more refusal. Section 5.7 of the RFC shows “Hello” as a client would send it, masked:
81 85 37 fa 21 3d 7f 9f 4d 51 58
The second byte, 85, has ValueError for any frame whose "masked" field is True. Chapter 4 teaches masking and gives the decoder the server’s side too.
Your task
In ws_frames.py, write decode_frame(read_exact, *, max_size=MAX_SIZE):
read 2 bytes and pass them to
parse_header_bytes, which raisesValueErrorfor reserved bits and; raise
ValueErrorif the frame is masked;get the payload length with
read_length;raise
ValueErrorif the length is overmax_size, before reading any payload;read the payload and return
{"fin": ..., "opcode": ..., "payload": ...}, with the payload asbytes.
Do not catch ConnectionError. If the peer closes in the middle of a frame, the caller needs to know.
Press Run. The Lesson 5 section decodes the RFC’s “Hello” fed to your decoder one byte at a time:
Lesson 5: Hello decoded one byte at a time
{'fin': True, 'opcode': 1, 'payload': b'Hello'}
Then press Submit. The check gives your decoder a stand-in read_exact that records every count it is asked for. It decodes the RFC’s frames, a fragment, binary frames of 256 and 65536 bytes with payloads you have not seen, and two frames sent back to back; it checks that an oversized frame is refused before its payload is asked for, that a masked frame, a reserved opcode and a non-minimal length raise ValueError, and that a close in the middle of a frame reaches the caller as ConnectionError.
The learning codec is complete for unmasked frames. The project that ends this chapter writes the tests that hold it to the RFC’s bytes.
Task
Write decode_frame(read_exact, *, max_size=MAX_SIZE) in ws_frames.py. It reads one unmasked read_exact and returns {"fin": ..., "opcode": ..., "payload": ...}:
read 2 bytes and pass them to
parse_header_bytes;raise
if the frame is masked; get the payload length with
read_length;raise
ValueErrorif the length is overmax_size, before asking for any payload byte;read exactly the payload, and return it as
byteswithfinand the.
Let a ConnectionError from read_exact reach the caller.
Press Run: the Lesson 5 section should print {'fin': True, 'opcode': 1, 'payload': b'Hello'}. Then press Submit. The check hands decode_frame a stand-in read_exact that records every count it is asked for. It expects the RFC’s text and ValueError for a frame over max_size with no request for its payload, a masked frame, a reserved opcode and a non-minimal length, and ConnectionError when the bytes stop in the middle of a frame. Your