61 lines
2.2 KiB
Python
61 lines
2.2 KiB
Python
"""The packet format every sender and receiver in this project uses. Given; do not change it.
|
|
|
|
0 1 5 7 9
|
|
+------+--------------+--------+----------+-----------------+
|
|
| type | seq (32) | len | checksum | payload (len) |
|
|
+------+--------------+--------+----------+-----------------+
|
|
|
|
All header fields are big-endian ("network order"). The checksum is the
|
|
Internet checksum (RFC 1071) over the whole packet with the checksum field set
|
|
to zero, the same one IPv4, UDP and TCP use.
|
|
|
|
References:
|
|
https://docs.python.org/3/library/struct.html
|
|
https://www.rfc-editor.org/rfc/rfc1071
|
|
"""
|
|
|
|
import struct
|
|
|
|
DATA = 0 # carries payload; seq numbers the packet, starting at 0
|
|
ACK = 1 # acknowledges; what seq means depends on the protocol (see README)
|
|
FIN = 2 # the sender is done; seq is the number of data packets it sent
|
|
FINACK = 3 # the receiver has everything and is closing
|
|
|
|
HEADER = struct.Struct("!BIHH")
|
|
MSS = 1000 # most payload bytes in one packet
|
|
|
|
|
|
def _checksum(data):
|
|
if len(data) % 2:
|
|
data += b"\0"
|
|
total = sum(struct.unpack(f"!{len(data) // 2}H", data))
|
|
while total >> 16:
|
|
total = (total & 0xFFFF) + (total >> 16)
|
|
return ~total & 0xFFFF
|
|
|
|
|
|
def encode(kind, seq, payload=b""):
|
|
"""Build one packet as bytes."""
|
|
if len(payload) > MSS:
|
|
raise ValueError(f"payload of {len(payload)} bytes is over the MSS of {MSS}")
|
|
header = HEADER.pack(kind, seq, len(payload), 0)
|
|
check = _checksum(header + payload)
|
|
return HEADER.pack(kind, seq, len(payload), check) + payload
|
|
|
|
|
|
def decode(raw):
|
|
"""Return (kind, seq, payload), or None if the packet is short or corrupted."""
|
|
if len(raw) < HEADER.size:
|
|
return None
|
|
kind, seq, length, check = HEADER.unpack_from(raw)
|
|
payload = raw[HEADER.size:]
|
|
if length != len(payload) or kind not in (DATA, ACK, FIN, FINACK):
|
|
return None
|
|
if _checksum(HEADER.pack(kind, seq, length, 0) + payload) != check:
|
|
return None
|
|
return kind, seq, payload
|
|
|
|
|
|
def split(data):
|
|
"""Cut a byte string into MSS-sized payloads, in order."""
|
|
return [data[i:i + MSS] for i in range(0, len(data), MSS)]
|