Current section

Files

Jump to
mist src mist@websocket.erl
Raw

src/mist@websocket.erl

-module(mist@websocket).
-compile(no_auto_import).
-export([frame_from_message/2, frame_to_bit_builder/1, upgrade/2, crypto_hash/2, base64_encode/1, parse_key/1, upgrade_socket/1, send/2, echo_handler/2, with_handler/1, on_init/2, on_close/2]).
-export_type([message/0, frame/0, sha_hash/0, websocket_handler/0]).
-type message() :: {binary_message, bitstring()} | {text_message, binary()}.
-type frame() :: {close_frame, integer(), bitstring()} |
{text_frame, integer(), bitstring()} |
{binary_frame, integer(), bitstring()} |
{ping_frame, integer(), bitstring()} |
{pong_frame, integer(), bitstring()}.
-type sha_hash() :: sha.
-type websocket_handler() :: {websocket_handler,
gleam@option:option(fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)),
gleam@option:option(fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)),
fun((message(), gleam@otp@process:sender(glisten@tcp:handler_message())) -> {ok,
nil} |
{error, nil})}.
-spec unmask_data(bitstring(), list(bitstring()), integer(), bitstring()) -> bitstring().
unmask_data(Data, Masks, Index, Resp) ->
case Data of
<<>> ->
Resp;
<<Masked:8/bitstring, Rest/bitstring>> ->
{ok, Mask_value@1} = case gleam@list:at(Masks, Index rem 4) of
{ok, Mask_value} -> {ok, Mask_value};
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/websocket"/utf8>>,
function => <<"unmask_data"/utf8>>,
line => 45})
end,
Unmasked = crypto:exor(Mask_value@1, Masked),
unmask_data(
Rest,
Masks,
Index
+ 1,
<<Resp/bitstring, Unmasked/bitstring>>
)
end.
-spec frame_from_message(glisten@tcp:socket(), bitstring()) -> {ok, frame()} |
{error, nil}.
frame_from_message(Socket, Message) ->
<<_@2:1, Rest@1/bitstring>> = case Message of
<<_@1:1, Rest/bitstring>> -> <<_@1:1, Rest/bitstring>>;
_try ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 61})
end,
<<_@4:3, Rest@3/bitstring>> = case Rest@1 of
<<_@3:3, Rest@2/bitstring>> -> <<_@3:3, Rest@2/bitstring>>;
_try@1 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@1,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 62})
end,
<<Opcode@1:4/integer, Rest@5/bitstring>> = case Rest@3 of
<<Opcode:4/integer, Rest@4/bitstring>> -> <<Opcode:4/integer,
Rest@4/bitstring>>;
_try@2 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@2,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 63})
end,
case Opcode@1 of
1 ->
<<1:1, Rest@7/bitstring>> = case Rest@5 of
<<1:1, Rest@6/bitstring>> -> <<1:1, Rest@6/bitstring>>;
_try@3 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@3,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 67})
end,
<<Payload_length@1:7/integer, Rest@9/bitstring>> = case Rest@7 of
<<Payload_length:7/integer, Rest@8/bitstring>> -> <<Payload_length:7/integer,
Rest@8/bitstring>>;
_try@4 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@4,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 68})
end,
{Payload_length@2, Rest@14} = case Payload_length@1 of
126 ->
<<Length@1:16/integer, Rest@11/bitstring>> = case Rest@9 of
<<Length:16/integer, Rest@10/bitstring>> -> <<Length:16/integer,
Rest@10/bitstring>>;
_try@5 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@5,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 71})
end,
{Length@1, Rest@11};
127 ->
<<Length@3:64/integer, Rest@13/bitstring>> = case Rest@9 of
<<Length@2:64/integer, Rest@12/bitstring>> -> <<Length@2:64/integer,
Rest@12/bitstring>>;
_try@6 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@6,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 75})
end,
{Length@3, Rest@13};
_@5 ->
{Payload_length@1, Rest@9}
end,
<<Mask1@1:8/bitstring,
Mask2@1:8/bitstring,
Mask3@1:8/bitstring,
Mask4@1:8/bitstring,
Rest@16/bitstring>> = case Rest@14 of
<<Mask1:8/bitstring,
Mask2:8/bitstring,
Mask3:8/bitstring,
Mask4:8/bitstring,
Rest@15/bitstring>> -> <<Mask1:8/bitstring,
Mask2:8/bitstring,
Mask3:8/bitstring,
Mask4:8/bitstring,
Rest@15/bitstring>>;
_try@7 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@7,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 80})
end,
Data = case Payload_length@2 - gleam@bit_string:byte_size(Rest@16) of
0 ->
unmask_data(
Rest@16,
[Mask1@1, Mask2@1, Mask3@1, Mask4@1],
0,
<<>>
);
Need ->
{ok, Needed@1} = case glisten@tcp:'receive'(Socket, Need) of
{ok, Needed} -> {ok, Needed};
_try@8 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@8,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 90})
end,
_pipe = Rest@16,
_pipe@1 = gleam@bit_string:append(_pipe, Needed@1),
unmask_data(
_pipe@1,
[Mask1@1, Mask2@1, Mask3@1, Mask4@1],
0,
<<>>
)
end,
_pipe@2 = case Opcode@1 of
1 ->
{text_frame, Payload_length@2, Data};
2 ->
{binary_frame, Payload_length@2, Data}
end,
{ok, _pipe@2};
2 ->
<<1:1, Rest@7/bitstring>> = case Rest@5 of
<<1:1, Rest@6/bitstring>> -> <<1:1, Rest@6/bitstring>>;
_try@3 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@3,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 67})
end,
<<Payload_length@1:7/integer, Rest@9/bitstring>> = case Rest@7 of
<<Payload_length:7/integer, Rest@8/bitstring>> -> <<Payload_length:7/integer,
Rest@8/bitstring>>;
_try@4 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@4,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 68})
end,
{Payload_length@2, Rest@14} = case Payload_length@1 of
126 ->
<<Length@1:16/integer, Rest@11/bitstring>> = case Rest@9 of
<<Length:16/integer, Rest@10/bitstring>> -> <<Length:16/integer,
Rest@10/bitstring>>;
_try@5 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@5,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 71})
end,
{Length@1, Rest@11};
127 ->
<<Length@3:64/integer, Rest@13/bitstring>> = case Rest@9 of
<<Length@2:64/integer, Rest@12/bitstring>> -> <<Length@2:64/integer,
Rest@12/bitstring>>;
_try@6 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@6,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 75})
end,
{Length@3, Rest@13};
_@5 ->
{Payload_length@1, Rest@9}
end,
<<Mask1@1:8/bitstring,
Mask2@1:8/bitstring,
Mask3@1:8/bitstring,
Mask4@1:8/bitstring,
Rest@16/bitstring>> = case Rest@14 of
<<Mask1:8/bitstring,
Mask2:8/bitstring,
Mask3:8/bitstring,
Mask4:8/bitstring,
Rest@15/bitstring>> -> <<Mask1:8/bitstring,
Mask2:8/bitstring,
Mask3:8/bitstring,
Mask4:8/bitstring,
Rest@15/bitstring>>;
_try@7 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@7,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 80})
end,
Data = case Payload_length@2 - gleam@bit_string:byte_size(Rest@16) of
0 ->
unmask_data(
Rest@16,
[Mask1@1, Mask2@1, Mask3@1, Mask4@1],
0,
<<>>
);
Need ->
{ok, Needed@1} = case glisten@tcp:'receive'(Socket, Need) of
{ok, Needed} -> {ok, Needed};
_try@8 ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _try@8,
module => <<"mist/websocket"/utf8>>,
function => <<"frame_from_message"/utf8>>,
line => 90})
end,
_pipe = Rest@16,
_pipe@1 = gleam@bit_string:append(_pipe, Needed@1),
unmask_data(
_pipe@1,
[Mask1@1, Mask2@1, Mask3@1, Mask4@1],
0,
<<>>
)
end,
_pipe@2 = case Opcode@1 of
1 ->
{text_frame, Payload_length@2, Data};
2 ->
{binary_frame, Payload_length@2, Data}
end,
{ok, _pipe@2};
8 ->
{ok, {close_frame, 0, <<>>}}
end.
-spec frame_to_bit_builder(frame()) -> gleam@bit_builder:bit_builder().
frame_to_bit_builder(Frame) ->
case Frame of
{text_frame, Payload_length, Payload} ->
make_frame(1, Payload_length, Payload);
{close_frame, Payload_length@1, Payload@1} ->
make_frame(8, Payload_length@1, Payload@1);
{binary_frame, Payload_length@2, Payload@2} ->
make_frame(2, Payload_length@2, Payload@2);
{pong_frame, Payload_length@3, Payload@3} ->
make_frame(10, Payload_length@3, Payload@3);
{ping_frame, _@1, _@2} ->
gleam@bit_builder:from_bit_string(<<>>)
end.
-spec make_frame(integer(), integer(), bitstring()) -> gleam@bit_builder:bit_builder().
make_frame(Opcode, Length, Payload) ->
Length_section = case Length of
Length@1 when Length@1 > 65535 ->
<<127:7, Length@1:64/integer>>;
Length@2 when Length@2 >= 126 ->
<<126:7, Length@2:16/integer>>;
_@1 ->
<<Length:7>>
end,
_pipe = <<1:1,
0:3,
Opcode:4,
0:1,
Length_section/bitstring,
Payload/bitstring>>,
gleam@bit_builder:from_bit_string(_pipe).
-spec upgrade(glisten@tcp:socket(), gleam@http@request:request(bitstring())) -> {ok,
nil} |
{error, nil}.
upgrade(Socket, Req) ->
case begin
_pipe = upgrade_socket(Req),
gleam@result:replace_error(_pipe, nil)
end of
{error, _try} -> {error, _try};
{ok, Resp} ->
case begin
_pipe@1 = Resp,
_pipe@2 = mist@encoder:to_bit_builder(_pipe@1),
_pipe@3 = glisten@tcp:send(Socket, _pipe@2),
gleam@result:replace_error(_pipe@3, nil)
end of
{error, _try@1} -> {error, _try@1};
{ok, _@1} ->
{ok, nil}
end
end.
-spec to_text_frame(bitstring()) -> gleam@bit_builder:bit_builder().
to_text_frame(Data) ->
Size = gleam@bit_string:byte_size(Data),
frame_to_bit_builder({text_frame, Size, Data}).
-spec to_binary_frame(bitstring()) -> gleam@bit_builder:bit_builder().
to_binary_frame(Data) ->
Size = gleam@bit_string:byte_size(Data),
frame_to_bit_builder({binary_frame, Size, Data}).
-spec crypto_hash(sha_hash(), binary()) -> binary().
crypto_hash(A, B) ->
crypto:hash(A, B).
-spec base64_encode(binary()) -> binary().
base64_encode(A) ->
base64:encode(A).
-spec parse_key(binary()) -> binary().
parse_key(Key) ->
_pipe = Key,
_pipe@1 = gleam@string:append(
_pipe,
<<"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"/utf8>>
),
_pipe@2 = crypto:hash(sha, _pipe@1),
base64:encode(_pipe@2).
-spec upgrade_socket(gleam@http@request:request(bitstring())) -> {ok,
gleam@http@response:response(gleam@bit_builder:bit_builder())} |
{error, gleam@http@request:request(bitstring())}.
upgrade_socket(Req) ->
case begin
_pipe = gleam@http@request:get_header(Req, <<"upgrade"/utf8>>),
gleam@result:replace_error(_pipe, Req)
end of
{error, _try} -> {error, _try};
{ok, _@1} ->
case begin
_pipe@1 = gleam@http@request:get_header(
Req,
<<"sec-websocket-key"/utf8>>
),
gleam@result:replace_error(_pipe@1, Req)
end of
{error, _try@1} -> {error, _try@1};
{ok, Key} ->
case begin
_pipe@2 = gleam@http@request:get_header(
Req,
<<"sec-websocket-version"/utf8>>
),
gleam@result:replace_error(_pipe@2, Req)
end of
{error, _try@2} -> {error, _try@2};
{ok, _@2} ->
Accept_key = parse_key(Key),
_pipe@3 = gleam@http@response:new(101),
_pipe@4 = gleam@http@response:set_body(
_pipe@3,
gleam@bit_builder:from_bit_string(<<""/utf8>>)
),
_pipe@5 = gleam@http@response:prepend_header(
_pipe@4,
<<"Upgrade"/utf8>>,
<<"websocket"/utf8>>
),
_pipe@6 = gleam@http@response:prepend_header(
_pipe@5,
<<"Connection"/utf8>>,
<<"Upgrade"/utf8>>
),
_pipe@7 = gleam@http@response:prepend_header(
_pipe@6,
<<"Sec-WebSocket-Accept"/utf8>>,
Accept_key
),
{ok, _pipe@7}
end
end
end.
-spec send(message(), gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil.
send(Message, Sender) ->
_pipe@2 = case Message of
{text_message, Data} ->
_pipe = Data,
_pipe@1 = gleam@bit_string:from_string(_pipe),
to_text_frame(_pipe@1);
{binary_message, Data@1} ->
to_binary_frame(Data@1)
end,
_pipe@3 = {send_message, _pipe@2},
gleam@otp@process:send(Sender, _pipe@3),
nil.
-spec echo_handler(
message(),
gleam@otp@process:sender(glisten@tcp:handler_message())
) -> {ok, nil} | {error, nil}.
echo_handler(Message, Sender) ->
_@1 = send(Message, Sender),
{ok, nil}.
-spec with_handler(
fun((message(), gleam@otp@process:sender(glisten@tcp:handler_message())) -> {ok,
nil} |
{error, nil})
) -> websocket_handler().
with_handler(Func) ->
{websocket_handler, none, none, Func}.
-spec on_init(
websocket_handler(),
fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)
) -> websocket_handler().
on_init(Handler, Func) ->
erlang:setelement(3, Handler, {some, Func}).
-spec on_close(
websocket_handler(),
fun((gleam@otp@process:sender(glisten@tcp:handler_message())) -> nil)
) -> websocket_handler().
on_close(Handler, Func) ->
erlang:setelement(2, Handler, {some, Func}).