-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; <> -> {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, <> ) 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, <> = case Rest@3 of <> -> <>; _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, <> = case Rest@7 of <> -> <>; _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 -> <> = case Rest@9 of <> -> <>; _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 -> <> = case Rest@9 of <> -> <>; _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, <> = case Rest@14 of <> -> <>; _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, <> = case Rest@7 of <> -> <>; _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 -> <> = case Rest@9 of <> -> <>; _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 -> <> = case Rest@9 of <> -> <>; _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, <> = case Rest@14 of <> -> <>; _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 -> <> 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}).