Packages

WebSocket protocol library for Erlang (RFC 6455 / RFC 8441 / RFC 9220)

Current section

Files

Jump to
erlang_ws CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project are documented here. Format loosely
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
follow [Semantic Versioning](https://semver.org/).
## [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.4.0]: https://github.com/benoitc/erlang_ws/releases/tag/0.4.0
## [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.3.0]: https://github.com/benoitc/erlang_ws/releases/tag/0.3.0
## [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.2.0]: https://github.com/benoitc/erlang_ws/releases/tag/0.2.0
## [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.1]: https://github.com/benoitc/erlang_ws/releases/tag/0.1.1
## [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_session``gen_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.
[0.1.0]: https://github.com/benoitc/erlang_ws/releases/tag/0.1.0