From 7ed8b12c4e7f5999d825c9603562d9eb81e8470a Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Tue, 21 Oct 2025 14:55:51 -0700 Subject: [PATCH] painting bike shed --- src/fd_ws.erl | 85 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/fd_ws.erl b/src/fd_ws.erl index e02087d..1aaa823 100644 --- a/src/fd_ws.erl +++ b/src/fd_ws.erl @@ -10,6 +10,7 @@ ]). -export([ + %% porcelain handshake/1, recv/2, recv/3, send/2 @@ -363,7 +364,7 @@ recv(Sock, Recv) -> Remainder :: binary(), Reason :: any(). % @doc -% Equivalent to recv(Socket, Received, []) +% Pull a message off the socket recv(Sock, Received, Frames) -> case maybe_pop_msg(Frames) of @@ -754,28 +755,58 @@ send_frame(Sock, Frame) -> % @private % render a frame % -% RSV is always <<0:3>> in rendered frame +% All fields in a `#frame{}` record have default values of `none`. % -% TODO: this doesn't check/do masking +% ```erlang +% -record(frame, +% {fin = none :: none | boolean(), +% rsv = none :: none | <<_:3>>, +% opcode = none :: none | opcode(), +% mask = none :: none | boolean(), +% payload_length = none :: none | non_neg_integer(), +% masking_key = none :: none | <<>> | <<_:32>>, +% payload = none :: none | binary()}). +% ``` % +% Given a value of `none`, some of these fields are inferred, some cannot be +% inferred. % -% This is a non-issue as long as this is only used for rendering messages sent -% from server to client (unmasked per protocol). However, for debugging -% purposes, a user of this library might want to test how frames render with -% masking. This functionality is not currently supported, but is a planned -% addition in the future. +% Inference cases: +% +% ``` +% rsv = none -> <<0:3>> +% mask = none -> false +% masking_key = none -> <<>> +% payload_length = none -> byte_size(Payload) +% ``` +% +% Non-inference: +% +% ``` +% fin +% opcode +% payload +% ``` % @end render_frame(#frame{fin = Fin, + rsv = RSV, opcode = Opcode, + mask = Mask, payload_length = Len, + masking_key = MaskingKey, payload = Payload}) -> BFin = case Fin of true -> <<1:1>>; false -> <<0:1>> end, - BRSV = <<0:3>>, + BRSV = + case RSV of + none -> <<0:3>>; + <<_:3>> -> RSV; + _ -> error({illegal_rsv, RSV}) + end, BOpcode = case Opcode of continuation -> << 0:1>>; @@ -785,15 +816,43 @@ render_frame(#frame{fin = Fin, ping -> << 9:1>>; pong -> <<10:1>> end, - BMask = <<0:1>>, - BPayloadLength = render_payload_length(Len), + BoolMask = + case Mask of + none -> false; + false -> false; + true -> true + end, + BMask = + case BoolMask of + true -> <<1:1>>; + false -> <<0:1>> + end, + IntPayloadLength = + case Len of + none -> byte_size(Payload); + _ -> Len + end, + BPayloadLength = render_payload_length(IntPayloadLength), + BMaskingKey = + case {BoolMask, MaskingKey} of + {false, none} -> <<>>; + {false, <<>>} -> <<>>; + {true, <>} -> BKey; + {false, _} -> error({not_masking_but_have_masking_key, {Mask, MaskingKey}}); + {true, _} -> error({illegal_masking_key, MaskingKey}) + end, + % failure case here is same as error case just above, so no need to worry + % about cryptic "illegal frame" message + % + % masking = unmasking, so `maybe_unmask` is a bit of a misnomer + {ok, BPayload} = maybe_unmask(#frame{}, BoolMask, BMaskingKey, Payload), <>. - + BMaskingKey/binary, + BPayload/binary>>. -spec render_payload_length(non_neg_integer()) -> binary().