Changelog

View Source

All notable changes to this project are documented here. Format loosely follows Keep a Changelog; versions follow Semantic Versioning.

[0.5.0] - 2026-08-10

Fixed

  • ws_client:connect/2 no longer loses a frame the server coalesced into the same TCP segment as its 101 response. The bytes read_response/4 returned past the end of the handshake were discarded, so a server that greets the instant the upgrade completes left the connection silent. Reported and fixed by @longlene (#3).

Added

  • initial_data start option, on ws_session:start/1 and ws:accept/6: bytes the embedder read past the end of the handshake. The session replays them through the parser before it arms the transport, so they stay ahead of whatever the peer sends next. Use it instead of posting a synthetic transport message to the session, which leaves the order to chance.

Changed

  • ws_h1_tcp_server hands the bytes it read past the request headers to the session through initial_data rather than forging a {tcp, Handle, Rest} message after ws:accept/6 had already armed the socket. The old path also only covered ws_transport_gen_tcp and ws_transport_ssl; any other transport hit a case_clause.
  • ws_h2_upgrade (and ws_h3_upgrade through it) now handles Sec-WebSocket-Version the way RFC 8441 section 5 requires: validate_request/1,2 rejects an extended CONNECT whose version is absent or not 13 with {error, {unsupported_version, V}}, matching ws_h1_upgrade, and surfaces version in request_info(); client_request/4,5 sends the header. Servers that were accepting version-less CONNECT streams will now reject them.

0.4.0 - 2026-08-07

Added

  • permessage-deflate (RFC 7692) is now wired end to end. The frame parser accepts RSV1 on the first frame of a data message when the compress parser option is on and delivers such messages as {compressed, text | binary, Payload}; ws_frame:encode_compressed/2 emits RSV1 frames. The session takes a deflate option (the negotiated parameters), inflates inbound messages bounded by max_message (a deflate bomb closes with 1009), validates text UTF-8 after inflating, and compresses outbound data frames with per-direction context takeover. ws_deflate:negotiate/2 and ws_deflate:parse_offer/1 negotiate straight from the raw Sec-WebSocket-Extensions elements the upgrade validators deliver.
  • ws_client:connect/2 accepts compress => true to offer permessage-deflate and run the session compressed when the server agrees.
  • ws_frame:valid_utf8/1: whole-payload UTF-8 check for consumers validating inflated text.

Changed

  • The peer's close frame now reaches the handler: terminate/2 receives {remote, Code, Reason} when the peer's close carried a status code, and remote for a bare close. Other shutdown reasons are passed through unchanged.

0.3.0 - 2026-05-29

Changed

  • Ported to Erlang/OTP 29. Old-style catch Expr cleanup calls in the test suites and examples are rewritten as try ... catch _:_ -> ok end to satisfy OTP 29's warn_deprecated_catch. The library sources were already free of old-style catch.
  • CI now tests OTP 28 and 29 (dropped 26 and 27) and uses rebar3 3.27.

0.2.0 - 2026-05-21

Security

  • ws_frame UTF-8 validation no longer crashes on a non-continuation byte in the middle of a multi-byte sequence (e.g. 0xC2 0x41). Such input is now reported as invalid_utf8 so the session closes with code 1007 instead of the process crashing with badarg.
  • ws_session now enforces an idle_timeout (default 60000 ms) on open connections and a close_timeout (default 5000 ms) while awaiting the peer's close echo. A stalled peer can no longer hold a session and socket open indefinitely. Both accept infinity to disable and are configurable via ws:accept/6, ws:connect/2, and ws_h1_tcp_server:start_link/1. Note: long-lived idle connections now require app-level pings or idle_timeout => infinity.

Fixes

  • ws_client:connect/2 returns {error, {invalid_port, _}} for a non-numeric URL port instead of crashing, and accepts IPv6 literal hosts such as ws://[::1]:8080/.
  • ws_frame:encode/2 caps an over-long close reason (> 123 bytes) on a UTF-8 codepoint boundary instead of crashing.

0.1.1 — 2026-04-19

Security

  • ws_h1_upgrade:validate_request/1,2 no longer crashes on a malformed Sec-WebSocket-Key header; invalid base64 now surfaces as {error, bad_sec_websocket_key}.
  • ws_deflate:inflate/3,4 caps inflated output (default 64 MiB, configurable) and returns {error, {inflate_too_big, _}} past the bound — defuses the classic permessage-deflate bomb.
  • ws_client:connect/2 caps pre-upgrade byte accumulation via a new max_handshake_size option (default 64 KiB). Exceeding it returns {error, handshake_response_too_big}.
  • ws_h1_tcp_server:start_link/1 gains the same max_handshake_size option (default 64 KiB). Over-limit returns {error, handshake_too_big} and the socket is closed.

Tests

  • New ws_security_SUITE with 11 targeted cases covering each of the above plus state-machine invariants (send during closing, handler init stop, ordered multi-frame send).

Documentation

  • docs/embedding.md — new "Hardening" section summarising the embedder-side defences: handshake caps, timeouts, concurrent connections, origin enforcement, deflate bombs, TLS defaults, close-code handling.
  • docs/errors.md — new "Size-limit errors" table.

Housekeeping

  • Apache-2.0 copyright header added to every .erl file.

0.1.0 — 2026-04-19

Initial release.

Frame codec

  • RFC 6455 encode / decode for all opcodes (text, binary, ping, pong, close, continuation).
  • Masking / unmasking (32-bit XOR), symmetric and streamable.
  • Fragmentation reassembly across continuation frames, with an open fragment state machine.
  • UTF-8 validation for text payloads and close reasons via an inlined Hoehrmann DFA, streaming-safe across fragment boundaries.
  • Close-code validation per RFC 6455 §7.4 (ws_close). Reserved codes 1004 / 1005 / 1006 / 1015 rejected on the wire; 3000–4999 accepted for registration / application use.
  • Size guards: max_frame and max_message options on the parser, enforced during single-frame decode and fragment accumulation.

Handshake helpers

  • ws_h1_upgrade — server-side validation of Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version; Sec-WebSocket-Accept generation; subprotocol negotiation; client-side key generation, request build, 101 response validation.
  • ws_h2_upgrade — RFC 8441 extended CONNECT pseudo-header validation (:method=CONNECT, :protocol=websocket, :scheme, :authority, :path); server response builder; client request builder that refuses to issue a CONNECT when the peer has not advertised SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  • ws_h3_upgrade — RFC 9220 mirror of the H2 helpers.

Session

  • ws_sessiongen_statem driving the session. States ready_wait -> open -> closing. Auto-responds to peer pings with matching pongs; surfaces ping / pong to the handler for custom behaviour.
  • Orderly close handshake: on receiving a close frame, echoes a close back (only once) before exiting normally. Peer-initiated protocol violations trigger a close frame with the matching code (1002 protocol error / 1007 invalid UTF-8 / 1009 message too big) followed by socket shutdown.
  • ws_handler behaviour: init/2, handle_in/2, handle_info/2, terminate/2. All callbacks can reply with outbound frames.

Transport layer

  • ws_transport behaviour — send/2, activate/1, close/1, controlling_process/2, classify/2, recv/2 (optional, used by the client during the HTTP/1.1 handshake), peername/1 (optional).
  • ws_transport_gen_tcp — reference gen_tcp transport used by tests.
  • ws_transport_ssl — OTP ssl transport used by wss:// client connections.

Reference server

  • ws_h1_tcp_server — a minimal gen_tcp acceptor + HTTP/1.1 upgrade driver built on top of the library helpers. Supports plain TCP and TLS via the tls option. Doubles as a runnable example and the server-side driver of ws_examples_SUITE.

Client

  • ws_client:connect/2 for ws:// / wss:// URLs. Builds the RFC 6455 upgrade request, validates the 101 response (including Sec-WebSocket-Accept cross-check), starts a ws_session in client mode with the user handler.
  • Automatic subprotocol, extension, origin, and extra-header handling on the request side.

Extensions

  • ws_deflate — RFC 7692 permessage-deflate.
    • Server-side negotiation of client offers with configurable takeover / window-bits policy; ignore-on-disagreement semantics.
    • Client-side offer builder and server-response parser.
    • Inflate / deflate primitives with takeover management.

Examples

  • examples/echo_server.erl — RFC 6455 echo server backed by ws_h1_tcp_server.
  • examples/echo_client.erl — synchronous send-and-wait client, uses ws:connect/2.
  • examples/chat_server.erl — broadcast chat server using pg for fan-out between sessions.

Tests

  • 142 EUnit tests across codec, close codes, handshake helpers, deflate.
  • 4 PropEr properties: mask involution, round-trip in both directions, chunked-delivery preserves messages.
  • 25 Common Test cases total:
    • 10 in ws_session_SUITE — text / binary / large-payload echo, ping/pong, fragmentation, orderly-close, server-initiated close, bad-UTF-8 rejection, oversize-frame rejection, handler info fan-out.
    • 3 in ws_client_SUITE — client↔server round-trip.
    • 12 in ws_examples_SUITE — end-to-end coverage of the examples/ modules plus subprotocol negotiation, 20 concurrent clients, TLS wss:// round-trip, 512 KiB payload, fragmented text delivered raw.
  • Autobahn compliance suite (WS_RUN_AUTOBAHN=1) — 300 cases across sections 1–9. All OK / NON-STRICT / INFORMATIONAL; zero failures.

Documentation

  • README.md — quickstart with server and client examples, module map, embedder integration notes.
  • docs/guide.md — tutorial (mental model, handler, server, client, sending frames, closing, subprotocols, TLS, limits). Every code snippet is mechanically verified by ws_docs_snippets_SUITE.
  • docs/embedding.md — HTTP/1.1, RFC 8441 (HTTP/2), and RFC 9220 (HTTP/3) integration patterns plus instructions for writing a custom ws_transport.
  • docs/errors.md — every failure mode the library surfaces, with close codes and remediation.
  • docs/features.md — RFC coverage, hardening list, scope boundaries, full module map.
  • LICENSE — Apache-2.0.