nhttp_ws_frame (nhttp_lib v1.0.4)

View Source

WebSocket 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).

Summary

Functions

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.

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.

Decode an unmasked WebSocket frame (server-to-client). RFC 6455 §5.1: a server MUST NOT mask frames sent to clients.

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.

Scan a run of text that continues an unfinished character.

Validate that a control frame is not fragmented and not too large (RFC 6455 §5.5: control frame payloads MUST be <= 125 bytes).

Types

close_code()

-type close_code() :: 0..65535.

decode_result()

-type decode_result() ::
          {ok, ws_message(), Rest :: binary()} | {more, MinBytes :: pos_integer()} | {error, term()}.

encode_opts()

-type encode_opts() :: #{mask => boolean()}.

raw_decode_result()

-type raw_decode_result() ::
          {ok, Fin :: 0 | 1, Opcode :: 0..15, Payload :: binary(), Rest :: binary()} |
          {more, MinBytes :: pos_integer()} |
          {error, term()}.

ws_message()

-type ws_message() ::
          {text, binary()} |
          {binary, binary()} |
          ping |
          {ping, binary()} |
          pong |
          {pong, binary()} |
          close |
          {close, Code :: close_code(), Reason :: binary()}.

ws_opcode()

-type ws_opcode() :: text | binary | close | ping | pong | continuation.

Functions

decode/1

-spec decode(binary()) -> 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.

decode_raw/2

-spec decode_raw(binary(), client | server) -> 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.

decode_unmasked/1

-spec decode_unmasked(binary()) -> decode_result().

Decode an unmasked WebSocket frame (server-to-client). RFC 6455 §5.1: a server MUST NOT mask frames sent to clients.

encode(Message)

-spec encode(ws_message()) -> iodata().

Encode a WebSocket message as an unmasked frame (server-to-client).

encode(Message, Opts)

-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).

encode_masked(Message)

-spec encode_masked(ws_message()) -> iodata().

Encode a WebSocket message as a masked frame (client-to-server).

opcode_to_complete_message/2

-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.

opcode_to_message/3

-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_utf8/1

-spec scan_utf8(binary()) -> {ok, Carry :: binary()} | {error, invalid_utf8}.

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_utf8/2

-spec scan_utf8(Carry :: binary(), binary()) -> {ok, Carry :: binary()} | {error, invalid_utf8}.

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_control_frame/3

-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).