nhttp_ws_frame (nhttp_lib v1.1.1)
View SourceWebSocket per-frame codec (RFC 6455).
Pure encode and decode for a single WebSocket frame. Stateless. For
continuation reassembly and interleaved control frames, layer
nhttp_ws on top of this module.
Server-to-client frames are unmasked, client-to-server frames must be masked (RFC 6455 §5.1).
Declared length cap
A frame header declares up to 2^63-1 payload bytes, and a decoder that trusts that number asks its caller to buffer it. RFC 6455 §10.4 names the threat: "a malicious endpoint can try to exhaust its peer's memory ... by sending either a single big frame (e.g., of size 2**60)".
Every decode function takes a frame_limits/0 map. When the declared
length is above max_frame_size, the decoder returns
{error, {frame_too_large, DeclaredLength}} at the moment the length is
read, before any payload is buffered. The caller closes with status 1009
(§7.4.1). The arities without a limits map do not cap.
The effective cap never drops below 125 bytes, because §5.5 makes that length legal for every control frame and §5.5.1 uses it for the close code and reason.
Summary
Functions
Decode a masked WebSocket frame (client-to-server) with no cap on the
declared payload length. Equivalent to decode(Data, #{}).
Decode a masked WebSocket frame (client-to-server).
Returns {ok, Message, Rest} on success, {more, MinBytes} if more
data is needed, or {error, Reason} on protocol violation.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
Decode a raw frame with no cap on the declared payload length.
Equivalent to decode_raw(Data, Role, #{}).
Decode a raw frame, returning Fin, Opcode, Payload, Rest separately.
Used by the stateful message-level decoder for continuation reassembly.
The role argument selects masked (server) or unmasked (client) parsing.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
Decode an unmasked WebSocket frame (server-to-client) with no cap on the
declared payload length. Equivalent to decode_unmasked(Data, #{}).
Decode an unmasked WebSocket frame (server-to-client).
RFC 6455 §5.1: a server MUST NOT mask frames sent to clients.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
Encode a WebSocket message as an unmasked frame (server-to-client).
Encode a WebSocket message with options. The only option is
mask => boolean(), which defaults to false (server-to-client).
Encode a WebSocket message as a masked frame (client-to-server).
Map a complete (FIN=1) frame's opcode and payload to a ws_message/0.
Text payloads are validated as UTF-8 (RFC 6455 §5.6 / §8.1).
Close payloads are validated against the reserved-code ranges of
RFC 6455 §7.4.1 and the reason is checked as UTF-8.
Map a frame's Fin bit, opcode and payload to a ws_message/0. Rejects
fragmented frames (Fin=0). Fragmentation must be handled by the
stateful message-level decoder.
Scan a run of text for UTF-8 validity (RFC 3629) and return the trailing bytes that do not yet form a character. A fragmented text message can split a character across two frames, so a fragment is not valid or invalid on its own: it ends in a carry, which the next fragment starts with. The run is refused as soon as no continuation can complete it, which puts the refusal on the fragment that breaks rather than on the whole reassembled message. A carry left at the end of a message is a truncated character, so the caller must require an empty one on the final fragment.
Scan a run of text that continues an unfinished character.
Carry comes from the previous fragment and is at most three bytes. Only
the bytes needed to finish that character are copied; the rest of the
payload is scanned where it lies.
Validate that a control frame is not fragmented and not too large (RFC 6455 §5.5: control frame payloads MUST be <= 125 bytes).
Types
-type close_code() :: 0..65535.
-type decode_result() :: {ok, ws_message(), Rest :: binary()} | {more, MinBytes :: pos_integer()} | {error, term()}.
-type encode_opts() :: #{mask => boolean()}.
-type frame_limits() :: #{max_frame_size => pos_integer() | infinity}.
-type frame_too_large() :: {frame_too_large, DeclaredLength :: pos_integer()}.
-type raw_decode_result() :: {ok, Fin :: 0 | 1, Opcode :: 0..15, Payload :: binary(), Rest :: binary()} | {more, MinBytes :: pos_integer()} | {error, term()}.
-type ws_opcode() :: text | binary | close | ping | pong | continuation.
Functions
-spec decode(binary()) -> decode_result().
Decode a masked WebSocket frame (client-to-server) with no cap on the
declared payload length. Equivalent to decode(Data, #{}).
-spec decode(binary(), frame_limits()) -> decode_result().
Decode a masked WebSocket frame (client-to-server).
Returns {ok, Message, Rest} on success, {more, MinBytes} if more
data is needed, or {error, Reason} on protocol violation.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
-spec decode_raw(binary(), client | server) -> raw_decode_result().
Decode a raw frame with no cap on the declared payload length.
Equivalent to decode_raw(Data, Role, #{}).
-spec decode_raw(binary(), client | server, frame_limits()) -> raw_decode_result().
Decode a raw frame, returning Fin, Opcode, Payload, Rest separately.
Used by the stateful message-level decoder for continuation reassembly.
The role argument selects masked (server) or unmasked (client) parsing.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
-spec decode_unmasked(binary()) -> decode_result().
Decode an unmasked WebSocket frame (server-to-client) with no cap on the
declared payload length. Equivalent to decode_unmasked(Data, #{}).
-spec decode_unmasked(binary(), frame_limits()) -> decode_result().
Decode an unmasked WebSocket frame (server-to-client).
RFC 6455 §5.1: a server MUST NOT mask frames sent to clients.
A declared payload length above max_frame_size returns
{error, {frame_too_large, DeclaredLength}} before any payload is
buffered (RFC 6455 §10.4).
-spec encode(ws_message()) -> iodata().
Encode a WebSocket message as an unmasked frame (server-to-client).
-spec encode(ws_message(), encode_opts()) -> iodata().
Encode a WebSocket message with options. The only option is
mask => boolean(), which defaults to false (server-to-client).
-spec encode_masked(ws_message()) -> iodata().
Encode a WebSocket message as a masked frame (client-to-server).
-spec opcode_to_complete_message(0..15, binary()) -> {ok, ws_message()} | {error, term()}.
Map a complete (FIN=1) frame's opcode and payload to a ws_message/0.
Text payloads are validated as UTF-8 (RFC 6455 §5.6 / §8.1).
Close payloads are validated against the reserved-code ranges of
RFC 6455 §7.4.1 and the reason is checked as UTF-8.
-spec opcode_to_message(0 | 1, 0..15, binary()) -> {ok, ws_message()} | {error, term()}.
Map a frame's Fin bit, opcode and payload to a ws_message/0. Rejects
fragmented frames (Fin=0). Fragmentation must be handled by the
stateful message-level decoder.
Scan a run of text for UTF-8 validity (RFC 3629) and return the trailing bytes that do not yet form a character. A fragmented text message can split a character across two frames, so a fragment is not valid or invalid on its own: it ends in a carry, which the next fragment starts with. The run is refused as soon as no continuation can complete it, which puts the refusal on the fragment that breaks rather than on the whole reassembled message. A carry left at the end of a message is a truncated character, so the caller must require an empty one on the final fragment.
Scan a run of text that continues an unfinished character.
Carry comes from the previous fragment and is at most three bytes. Only
the bytes needed to finish that character are copied; the rest of the
payload is scanned where it lies.
-spec validate_control_frame(0 | 1, 0..15, binary()) -> ok | {error, control_frame_too_large | fragmented_control_frame}.
Validate that a control frame is not fragmented and not too large (RFC 6455 §5.5: control frame payloads MUST be <= 125 bytes).