-module(mist@internal@websocket). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([frame_from_message/3, frame_to_bytes_builder/1, to_text_frame/1, to_binary_frame/1, initialize_connection/5]). -export_type([data_frame/0, control_frame/0, frame/0, valid_message/1, websocket_message/1, websocket_connection/0]). -type data_frame() :: {text_frame, integer(), bitstring()} | {binary_frame, integer(), bitstring()}. -type control_frame() :: {close_frame, integer(), bitstring()} | {ping_frame, integer(), bitstring()} | {pong_frame, integer(), bitstring()}. -type frame() :: {data, data_frame()} | {control, control_frame()}. -type valid_message(KHU) :: {internal, frame()} | socket_closed | {user, KHU}. -type websocket_message(KHV) :: {valid, valid_message(KHV)} | invalid. -type websocket_connection() :: {websocket_connection, glisten@socket:socket(), glisten@socket@transport:transport()}. -spec unmask_data(bitstring(), list(bitstring()), integer(), bitstring()) -> bitstring(). unmask_data(Data, Masks, Index, Resp) -> case Data of <<>> -> Resp; <> -> _assert_subject = gleam@list:at(Masks, Index rem 4), {ok, Mask_value} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mist/internal/websocket"/utf8>>, function => <<"unmask_data"/utf8>>, line => 48}) end, Unmasked = crypto:exor(Mask_value, Masked), unmask_data( Rest, Masks, Index + 1, <> ) end. -spec frame_from_message( glisten@socket:socket(), glisten@socket@transport:transport(), bitstring() ) -> {ok, frame()} | {error, nil}. frame_from_message(Socket, Transport, Message) -> <<_:1, Rest/bitstring>> = case Message of <<_:1, _/bitstring>> -> Message; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 60}) end, <<_:3, Rest@1/bitstring>> = case Rest of <<_:3, _/bitstring>> -> Rest; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 61}) end, <> = case Rest@1 of <<_:4/integer, _/bitstring>> -> Rest@1; _assert_fail@2 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 62}) end, <<1:1, Rest@3/bitstring>> = case Rest@2 of <<1:1, _/bitstring>> -> Rest@2; _assert_fail@3 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@3, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 64}) end, <> = case Rest@3 of <<_:7/integer, _/bitstring>> -> Rest@3; _assert_fail@4 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@4, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 65}) end, {Payload_length@1, Rest@7} = case Payload_length of 126 -> <> = case Rest@4 of <<_:16/integer, _/bitstring>> -> Rest@4; _assert_fail@5 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@5, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 68}) end, {Length, Rest@5}; 127 -> <> = case Rest@4 of <<_:64/integer, _/bitstring>> -> Rest@4; _assert_fail@6 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@6, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 72}) end, {Length@1, Rest@6}; _ -> {Payload_length, Rest@4} end, <> = case Rest@7 of <<_:8/bitstring, _:8/bitstring, _:8/bitstring, _:8/bitstring, _/bitstring>> -> Rest@7; _assert_fail@7 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@7, module => <<"mist/internal/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 77}) end, _pipe@5 = case Payload_length@1 - erlang:byte_size(Rest@8) of 0 -> {ok, unmask_data(Rest@8, [Mask1, Mask2, Mask3, Mask4], 0, <<>>)}; Need -> _pipe = Need, _pipe@1 = (erlang:element(10, Transport))(Socket, _pipe), _pipe@2 = gleam@result:replace_error(_pipe@1, nil), gleam@result:map(_pipe@2, fun(Needed) -> _pipe@3 = Rest@8, _pipe@4 = gleam@bit_array:append(_pipe@3, Needed), unmask_data(_pipe@4, [Mask1, Mask2, Mask3, Mask4], 0, <<>>) end) end, gleam@result:map(_pipe@5, fun(Data) -> case Opcode of 1 -> {data, {text_frame, Payload_length@1, Data}}; 2 -> {data, {binary_frame, Payload_length@1, Data}}; 8 -> {control, {close_frame, Payload_length@1, Data}}; 9 -> {control, {ping_frame, Payload_length@1, Data}}; 10 -> {control, {pong_frame, Payload_length@1, Data}} end end). -spec make_frame(integer(), integer(), bitstring()) -> gleam@bytes_builder:bytes_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>>; _ -> <> end, _pipe = <<1:1, 0:3, Opcode:4, 0:1, Length_section/bitstring, Payload/bitstring>>, gleam_stdlib:wrap_list(_pipe). -spec frame_to_bytes_builder(frame()) -> gleam@bytes_builder:bytes_builder(). frame_to_bytes_builder(Frame) -> case Frame of {data, {text_frame, Payload_length, Payload}} -> make_frame(1, Payload_length, Payload); {control, {close_frame, Payload_length@1, Payload@1}} -> make_frame(8, Payload_length@1, Payload@1); {data, {binary_frame, Payload_length@2, Payload@2}} -> make_frame(2, Payload_length@2, Payload@2); {control, {pong_frame, Payload_length@3, Payload@3}} -> make_frame(10, Payload_length@3, Payload@3); {control, {ping_frame, Payload_length@4, Payload@4}} -> make_frame(9, Payload_length@4, Payload@4) end. -spec to_text_frame(bitstring()) -> gleam@bytes_builder:bytes_builder(). to_text_frame(Data) -> Size = erlang:byte_size(Data), frame_to_bytes_builder({data, {text_frame, Size, Data}}). -spec to_binary_frame(bitstring()) -> gleam@bytes_builder:bytes_builder(). to_binary_frame(Data) -> Size = erlang:byte_size(Data), frame_to_bytes_builder({data, {binary_frame, Size, Data}}). -spec initialize_connection( fun(() -> {KIE, gleam@option:option(gleam@erlang@process:selector(KIF))}), fun((KIE) -> nil), fun((KIE, websocket_connection(), valid_message(KIF)) -> gleam@otp@actor:next(KIF, KIE)), glisten@socket:socket(), glisten@socket@transport:transport() ) -> {ok, gleam@erlang@process:subject(websocket_message(KIF))} | {error, nil}. initialize_connection(On_init, On_close, Handler, Socket, Transport) -> Connection = {websocket_connection, Socket, Transport}, _pipe@26 = gleam@otp@actor:start_spec( {spec, fun() -> {Initial_state, User_selector} = On_init(), Selector@1 = begin _pipe = gleam_erlang_ffi:new_selector(), _pipe@7 = gleam@erlang@process:selecting_record3( _pipe, erlang:binary_to_atom(<<"tcp"/utf8>>), fun(_, Data) -> _pipe@1 = Data, _pipe@2 = gleam@dynamic:bit_array(_pipe@1), _pipe@3 = gleam@result:replace_error(_pipe@2, nil), _pipe@4 = gleam@result:then( _pipe@3, fun(_capture) -> frame_from_message( Socket, Transport, _capture ) end ), _pipe@5 = gleam@result:map( _pipe@4, fun(Field@0) -> {internal, Field@0} end ), _pipe@6 = gleam@result:map( _pipe@5, fun(Field@0) -> {valid, Field@0} end ), gleam@result:unwrap(_pipe@6, invalid) end ), _pipe@14 = gleam@erlang@process:selecting_record3( _pipe@7, erlang:binary_to_atom(<<"ssl"/utf8>>), fun(_, Data@1) -> _pipe@8 = Data@1, _pipe@9 = gleam@dynamic:bit_array(_pipe@8), _pipe@10 = gleam@result:replace_error(_pipe@9, nil), _pipe@11 = gleam@result:then( _pipe@10, fun(_capture@1) -> frame_from_message( Socket, Transport, _capture@1 ) end ), _pipe@12 = gleam@result:map( _pipe@11, fun(Field@0) -> {internal, Field@0} end ), _pipe@13 = gleam@result:map( _pipe@12, fun(Field@0) -> {valid, Field@0} end ), gleam@result:unwrap(_pipe@13, invalid) end ), _pipe@15 = gleam@erlang@process:selecting_record2( _pipe@14, erlang:binary_to_atom(<<"ssl_closed"/utf8>>), fun(_) -> {valid, socket_closed} end ), _pipe@16 = gleam@erlang@process:selecting_record2( _pipe@15, erlang:binary_to_atom(<<"tcp_closed"/utf8>>), fun(_) -> {valid, socket_closed} end ), (fun(Selector) -> case User_selector of {some, User_selector@1} -> _pipe@17 = User_selector@1, _pipe@18 = gleam_erlang_ffi:map_selector( _pipe@17, fun(Field@0) -> {user, Field@0} end ), _pipe@19 = gleam_erlang_ffi:map_selector( _pipe@18, fun(Field@0) -> {valid, Field@0} end ), gleam_erlang_ffi:merge_selector( _pipe@19, Selector ); _ -> Selector end end)(_pipe@16) end, {ready, Initial_state, Selector@1} end, 500, fun(Msg, State) -> case Msg of {valid, {internal, {control, {close_frame, _, _}} = Frame}} -> _ = (erlang:element(12, erlang:element(3, Connection)))( erlang:element(2, Connection), frame_to_bytes_builder(Frame) ), On_close(State), {stop, normal}; {valid, {internal, {control, {ping_frame, Length, Payload}}}} -> _pipe@20 = (erlang:element( 12, erlang:element(3, Connection) ))( erlang:element(2, Connection), frame_to_bytes_builder( {control, {pong_frame, Length, Payload}} ) ), _pipe@21 = gleam@result:map( _pipe@20, fun(_) -> gleam@otp@actor:continue(State) end ), gleam@result:lazy_unwrap( _pipe@21, fun() -> On_close(State), {stop, {abnormal, <<"Failed to send pong frame"/utf8>>}} end ); invalid -> mist@internal@logger:error( {<<"Received a malformed Websocket frame"/utf8>>} ), gleam@otp@actor:continue(State); {valid, Msg@1} -> _pipe@22 = gleam_erlang_ffi:rescue( fun() -> Handler(State, Connection, Msg@1) end ), _pipe@25 = gleam@result:map( _pipe@22, fun(Cont) -> case Cont of {continue, State@1, Selector@2} -> _pipe@23 = Selector@2, _pipe@24 = gleam@option:map( _pipe@23, fun(_capture@2) -> gleam_erlang_ffi:map_selector( _capture@2, fun(Msg@2) -> {valid, {user, Msg@2}} end ) end ), {continue, State@1, _pipe@24}; {stop, Reason} -> On_close(State), {stop, Reason} end end ), gleam@result:lazy_unwrap( _pipe@25, fun() -> mist@internal@logger:error( <<"Caught error in websocket handler"/utf8>> ), On_close(State), {stop, {abnormal, <<"Websocket terminated"/utf8>>}} end ) end end} ), _pipe@27 = gleam@result:replace_error(_pipe@26, nil), _pipe@28 = gleam@result:map( _pipe@27, fun(Subj) -> Websocket_pid = gleam@erlang@process:subject_owner(Subj), _assert_subject = (erlang:element(5, erlang:element(3, Connection)))( erlang:element(2, Connection), Websocket_pid ), {ok, _} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mist/internal/websocket"/utf8>>, function => <<"initialize_connection"/utf8>>, line => 280}) end, _assert_subject@1 = (erlang:element( 13, erlang:element(3, Connection) ))(erlang:element(2, Connection), [{active_mode, once}]), {ok, _} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"mist/internal/websocket"/utf8>>, function => <<"initialize_connection"/utf8>>, line => 282}) end, Subj end ), gleam@result:replace_error(_pipe@28, nil).