Almost done... have to fix send

and then of course test it

there will be no bugs, right?
This commit is contained in:
Peter Harpending 2025-10-21 12:10:28 -07:00
parent 5824aaaf36
commit 1865f03085

View File

@ -391,7 +391,7 @@ recv(Sock, Received, Frames) ->
Message :: ws_msg(),
NewFrames :: Frames,
Reason :: any().
% @doc
% @private
% try to parse the stack of frames into a single message
%
% ignores RSV bits
@ -400,14 +400,100 @@ recv(Sock, Received, Frames) ->
maybe_pop_msg([]) ->
incomplete;
% case 1: control frames
maybe_pop_msg([Frame = #frame{opcode = Opcode} | Frames])
when Opcode =:= close; Opcode =:= ping; Opcode =:= pong ->
% note that maybe_control_msg checks that the fin bit is true
%
% meaning if the client sends a malicious control frame with fin=false, that
% error will be caught in maybe_control_msg
maybe_pop_msg([Frame = #frame{opcode = ControlOpcode} | Frames])
when (ControlOpcode =:= close)
orelse (ControlOpcode =:= ping)
orelse (ControlOpcode =:= pong) ->
case maybe_control_msg(Frame) of
{ok, Msg} -> {ok, Msg, Frames};
Error -> Error
end;
maybe_pop_msg(_) ->
error(nyi).
% case 2: messages
% finished message in a single frame, just pull here
maybe_pop_msg([Frame = #frame{fin = true,
opcode = DataOpcode,
mask = Mask,
masking_key = Key,
payload = Payload}
| Rest])
when DataOpcode =:= text; DataOpcode =:= binary ->
case maybe_unmask(Frame, Mask, Key, Payload) of
{ok, Unmasked} ->
Message = {DataOpcode, Unmasked},
{ok, Message, Rest};
Error ->
Error
end;
% end of a long message
maybe_pop_msg(Frames = [#frame{fin = true,
opcode = continuation} | _]) ->
maybe_long_data_msg(Frames);
% unfinished message, say we need more
maybe_pop_msg([#frame{fin = false,
opcode = continuation}
| _]) ->
incomplete;
% wtf... this case should be impossible
maybe_pop_msg([Frame | _]) ->
{error, {wtf_frame, Frame}}.
-spec maybe_long_data_msg(Frames) -> Result
when Frames :: [frame()],
Result :: {ok, Message, NewFrames}
| {error, Reason},
Message :: ws_msg(),
NewFrames :: Frames,
Reason :: any().
% @private
% assumes:
% 1. top of stack is a finished frame
% 2. top opcode is continuation
% 3. the stack corresponds to a linear sequence of frames all corresponding to
% one message, until we get to the leading frame of the message, which must
% have opcode text|binary
%
% the reason we can make this assumption is because anterior in the call
% chain is recv/3, which eagerly consumes control messages
%
% meaning if we encounter a control frame in the middle here, we can assume
% there is some sort of bug
%
% TODO: I am NOT enforcing that the data message consumes the entire stack of
% frames. Given that the context here is eager consumption, this might be a
% point of enforcement. Need to think about this.
% @end
maybe_long_data_msg(Frames) ->
mldm(Frames, Frames, <<>>).
% general case: decode the payload in this frame
mldm(OrigFrames, [Frame | Rest], Acc) ->
Opcode = Frame#frame.opcode,
Mask = Frame#frame.mask,
Key = Frame#frame.masking_key,
Payload = Frame#frame.payload,
case maybe_unmask(Frame, Mask, Key, Payload) of
{ok, Unmasked} ->
NewAcc = <<Unmasked/binary, Acc/binary>>,
case Opcode of
continuation -> mldm(OrigFrames, Rest, NewAcc);
text -> {ok, {text, NewAcc}, Rest};
binary -> {ok, {binary, NewAcc}, Rest};
_ -> {error, {illegal_data_frame, Frame, OrigFrames, Acc}}
end;
Error ->
Error
end;
% out of frames
mldm(OrigFrames, [], Acc) ->
{error, {no_start_frame, Acc, OrigFrames}}.
@ -419,6 +505,9 @@ maybe_pop_msg(_) ->
Reason :: any().
% @private
% assume the frame is a control frame, validate it, and unmask the payload
%
% TODO: this doesn't enforce that messages from the client HAVE to be masked,
% which strictly speaking is part of the protocol.
maybe_control_msg(F = #frame{fin = true,
opcode = Opcode,
@ -611,11 +700,13 @@ recv_frame_await(Frame, Sock, Received) ->
Reason :: closed | {timeout, RestData} | inet:posix(),
RestData :: binary() | erlang:iovec().
% @doc
% FIXME: this should be sending a message, not an arbitrary payload
%
% send binary data over Socket. handles frame nonsense
%
% types the payload as bytes
%
% max payload size is 2^64 - 1 bytes
% max payload size is 2^63 - 1 bytes
% @end
send(Socket, Payload) ->