0%

Frames, Byte by Byte · practice

Read the First Two Bytes

Every starts with the same two bytes, and those two bytes tell a reader everything it needs to know before the rest arrives: whether this frame ends a message, what kind of frame it is, whether its payload is masked, and how long the payload is, or where to find out. Here is the whole frame, laid out byte by byte after the diagram in RFC 6455, section 5.2, and filled in for the RFC’s “Hello” frame, 81 05 48 65 6c 6c 6f. Read the two bit rows first: each box is one bit, and the labels above them say which field the bit belongs to.

Byte 0 of the Hello frame, 0x81, holds FIN 1, three zero reserved bits and opcode 1; byte 1, 0x05, holds MASK 0 and the length 5; longer length bytes and a masking key appear only in some frames, and the 5-byte payload comes last.

In words, a frame is, in order:

  • byte 0: FIN, RSV1, RSV2 and RSV3, one bit each, then a 4-bit ;

  • byte 1: , one bit, then a 7-bit payload length;

  • 2 more length bytes, only if the 7-bit length is 126, or 8 more length bytes, only if it is 127;

  • a 4-byte masking key, only if MASK is 1;

  • last, the payload.

In the “Hello” frame, byte 0 is 0x81: FIN 1, the reserved bits 0, and opcode 1, text. Byte 1 is 0x05: MASK 0 and length 5. No longer length and no masking key follow, so the five payload bytes 48 65 6c 6c 6f, “Hello”, come next.

Byte 0 is the byte you took apart in Lesson 1. Byte 1 holds one more flag and a 7-bit number. That number is a length prefix, the same idea as the 4-byte header in Networks with Python Chapter 8: it tells the reader how many payload bytes follow. Seven bits only reach 127, though, so two of its values mean something else, and Lesson 4 is about them. The masking key belongs to Chapter 4. This lesson reads the two bytes that every frame has.

Byte 0

The top bit, 0x80, is FIN, short for final. A set FIN bit means this frame is the last piece of its message. A large message may be sent as several frames, called fragments, with FIN clear on all but the last. Every message in this chapter fits in one frame, so FIN is set in the frames you build, except once in Lesson 3, where you clear it to see that the bit is yours; Chapter 4 sends whole messages in fragments.

The next three bits, 0x40, 0x20 and 0x10, are RSV1, RSV2 and RSV3, the reserved bits. They are free for extensions that both sides agree on during the ; compression, for example, uses RSV1. This course agrees on no extension, so all three must be 0, and a frame with any of them set is refused. (b0 >> 4) & 0x07 moves them to the bottom and keeps only those three, giving a number from 0 to 7 that must be 0.

The low four bits, b0 & 0x0F, are the opcode: the kind of frame. RFC 6455 defines six of the sixteen possible values:

opcodeframe
0x0continuation, a later piece of a fragmented message
0x1text, a message in
0x2binary, a message of any bytes
0x3 to 0x7reserved
0x8close
0x9
0xApong
0xB to 0xFreserved

The opcodes from 0x8 up are control frames: they manage the connection instead of carrying a message, and Chapter 4 is about them. Reserved opcodes are set aside for future versions of the protocol. No peer may send one today, so a reader refuses any frame that uses one. ws_frames.py already the six defined opcodes in KNOWN_OPCODES.

A frame starts with the byte 0x89. What is it?

Why must parse_header_bytes refuse a frame whose first byte is 0x83?

Byte 1

The top bit of byte 1, 0x80, is MASK. When it is set, a 4-byte masking key follows the length, and the payload is scrambled with it. Clients mask every frame they send and servers never do; Chapter 4 explains why. For now, your parser only reports the flag.

The other seven bits, b1 & 0x7F, are the payload length. The mask 0x7F is 01111111: it keeps seven bits and clears MASK, so a masked frame of 5 bytes, 0x85, still reads as length 5. As Lesson 1 showed, & binds tighter than !=, so b1 & 0x80 != 0 is already the comparison (b1 & 0x80) != 0, which is True or False.

What does this print?

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

Your takes the two bytes as numbers. A frame arrives as a bytes , and a bytes value gives the number in that byte, so for a frame frame, frame[0] is b0 and frame[1] is b1. Here is a small binary frame with a 3-byte payload, read that way:

Try it

not binds more loosely than &, so not b1 & 0x80 asks whether the MASK bit is clear. This chapter’s program calls your function the same way, on the RFC’s “Hello” frame.

Your task

In ws_frames.py, write parse_header_bytes(b0, b1). Return a with five keys:

  • "fin": True if the top bit of b0 is set, else False;

  • "rsv": the three reserved bits as a number, (b0 >> 4) & 0x07;

  • "opcode": the low four bits of b0;

  • "masked": True if the top bit of b1 is set, else False;

  • "length": the low seven bits of b1.

Raise if any reserved bit is set, or if the opcode is not in KNOWN_OPCODES; an that names the bad value makes the message useful. Make "fin" and "masked" real True or False values, not 128 or 0: compare with 0 as above.

Press Run. The Lesson 2 section now reads the first two bytes of the RFC’s text frame for “Hello”:

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

A final text frame, unmasked, with 5 bytes of payload to come. Then press Submit. The check calls your function on frame headers you have not seen, including a masked one, one that is not final, and every reserved bit and several reserved opcodes on their own.

You can read a frame’s header now. The next lesson writes one: encode_frame builds the “Hello” frame these two bytes came from.

Task

Write parse_header_bytes(b0, b1) in ws_frames.py. It takes the first two bytes of a as numbers and returns a :

  • "fin": True when the top bit of b0 (0x80) is set, else False;

  • "rsv": the three reserved bits as a number, (b0 >> 4) & 0x07;

  • "opcode": the low four bits of b0;

  • "masked": True when the top bit of b1 is set, else False;

  • "length": the low seven bits of b1.

Raise when any reserved bit is set, and when the is not in KNOWN_OPCODES.

Press Run to see the Lesson 2 section, then press Submit. The check calls parse_header_bytes with header bytes you have not seen: frames that are and are not final, masked and unmasked, with the lengths 0, 125, 126 and 127, each reserved bit set on its own, and several reserved opcodes. "fin" and "masked" must be True or False. Your dict may hold extra keys. Leave the other in ws_frames.py as they are.