networking · advanced · ~15 min

Parse the 5-byte TLS record header

Read a tiny but real wire-protocol header.

Challenge

Every TLS connection starts with a 5-byte record header. Parse it from a byte buffer — no crypto involved, just reading fixed fields.

The header layout is:

byte 0:    content_type           (20=ChangeCipherSpec, 21=Alert, 22=Handshake, 23=ApplicationData)
byte 1:    legacy_version_major    (always 0x03)
byte 2:    legacy_version_minor    (0x01=TLS 1.0, 0x02=1.1, 0x03=1.2, 0x04=1.3)
bytes 3-4: length                  (big-endian, <= 0x4000 for a valid TLS record)

Task

Implement int parse_tls_record_header(const unsigned char *buf, int len, int *out_type, int *out_minor, int *out_length) that parses the header and fills the three output values.

Input

  • buf, len: the record bytes and how many are available.
  • out_type, out_minor, out_length: set to content_type, legacy_version_minor, and length on success.

Output

Returns 1 on success (header valid and outputs populated), or 0 otherwise.

Example

{22, 3, 3, 0x01, 0x00}   ->   1, type=22, minor=3, length=256
{23, 3, 4, 0x50, 0x00}   ->   0   (length 0x5000 exceeds 0x4000)

Edge cases

  • Reject if fewer than 5 bytes are available.
  • Reject if the major version byte is not 0x03.
  • Reject if the length exceeds 0x4000 (the max valid TLS record length).
  • Length read as big-endian: (buf[3] << 8) | buf[4].

Why this matters

Every TLS connection starts with a 5-byte record header. Knowing the layout demystifies the protocol; you can parse a capture without writing crypto.

Input format

Record bytes (buf) and available length (len), plus three out-pointers for type, minor version, and length.

Output format

Returns 1 with the three outputs populated on success, or 0 otherwise.

Constraints

Require >= 5 bytes; require major version 0x03; read length big-endian and reject if > 0x4000.

Starter code

#include <stddef.h>
int parse_tls_record_header(const unsigned char *buf, int len, int *out_type, int *out_minor, int *out_length) { /* TODO */ (void)buf; (void)len; (void)out_type; (void)out_minor; (void)out_length; return 0; }

Common mistakes

Reading length in little-endian. Accepting length > 0x4000.

Edge cases to handle

Length exactly 0x4000. Type outside the known set (allowed but uncommon).

Complexity

O(1).

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.