-module(mist@websocket). -compile([no_auto_import, nowarn_unused_vars]). -export([frame_to_bit_builder/1, send/2, echo_handler/2, with_handler/1, on_init/2, on_close/2, crypto_hash/2, base64_encode/1, parse_key/1, frame_from_message/3]). -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@erlang@process:subject(glisten@handler:handler_message())) -> nil)), gleam@option:option(fun((gleam@erlang@process:subject(glisten@handler:handler_message())) -> nil)), fun((message(), gleam@erlang@process:subject(glisten@handler:handler_message())) -> {ok, nil} | {error, nil})}. -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>>; _ -> <> end, _pipe = <<1:1, 0:3, Opcode:4, 0:1, Length_section/bitstring, Payload/bitstring>>, gleam@bit_builder:from_bit_string(_pipe). -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, _, _} -> gleam@bit_builder:from_bit_string(<<>>) 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 send( gleam@erlang@process:subject(glisten@handler:handler_message()), message() ) -> nil. send(Sender, Message) -> _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@erlang@process:send(Sender, _pipe@3), nil. -spec echo_handler( message(), gleam@erlang@process:subject(glisten@handler:handler_message()) ) -> {ok, nil} | {error, nil}. echo_handler(Message, Sender) -> _ = send(Sender, Message), {ok, nil}. -spec with_handler( fun((message(), gleam@erlang@process:subject(glisten@handler:handler_message())) -> {ok, nil} | {error, nil}) ) -> websocket_handler(). with_handler(Func) -> {websocket_handler, none, none, Func}. -spec on_init( websocket_handler(), fun((gleam@erlang@process:subject(glisten@handler:handler_message())) -> nil) ) -> websocket_handler(). on_init(Handler, Func) -> erlang:setelement(3, Handler, {some, Func}). -spec on_close( websocket_handler(), fun((gleam@erlang@process:subject(glisten@handler:handler_message())) -> nil) ) -> websocket_handler(). on_close(Handler, Func) -> erlang:setelement(2, Handler, {some, Func}). -spec crypto_hash(sha_hash(), binary()) -> binary(). crypto_hash(Field@0, Field@1) -> crypto:hash(Field@0, Field@1). -spec base64_encode(binary()) -> binary(). base64_encode(Field@0) -> base64:encode(Field@0). -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 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mist/websocket"/utf8>>, function => <<"unmask_data"/utf8>>, line => 43}) 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mist/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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"mist/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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 62}) end, case Opcode of 1 -> <<1:1, Rest@3/bitstring>> = case Rest@2 of <<1:1, _/bitstring>> -> Rest@2; _assert_fail@3 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@3, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 66}) end, <> = case Rest@3 of <<_:7/integer, _/bitstring>> -> Rest@3; _assert_fail@4 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@4, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 67}) 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@5, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 70}) end, {Length, Rest@5}; 127 -> <> = case Rest@4 of <<_:64/integer, _/bitstring>> -> Rest@4; _assert_fail@6 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@6, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 74}) 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@7, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 79}) end, Data = case Payload_length@1 - gleam@bit_string:byte_size(Rest@8) of 0 -> unmask_data(Rest@8, [Mask1, Mask2, Mask3, Mask4], 0, <<>>); Need -> _assert_subject = (erlang:element(9, Transport))( Socket, Need ), {ok, Needed} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail@8 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@8, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 89}) end, _pipe = Rest@8, _pipe@1 = gleam@bit_string:append(_pipe, Needed), unmask_data(_pipe@1, [Mask1, Mask2, Mask3, Mask4], 0, <<>>) end, _pipe@2 = case Opcode of 1 -> {text_frame, Payload_length@1, Data}; 2 -> {binary_frame, Payload_length@1, Data} end, {ok, _pipe@2}; 2 -> <<1:1, Rest@3/bitstring>> = case Rest@2 of <<1:1, _/bitstring>> -> Rest@2; _assert_fail@3 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@3, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 66}) end, <> = case Rest@3 of <<_:7/integer, _/bitstring>> -> Rest@3; _assert_fail@4 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@4, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 67}) 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@5, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 70}) end, {Length, Rest@5}; 127 -> <> = case Rest@4 of <<_:64/integer, _/bitstring>> -> Rest@4; _assert_fail@6 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@6, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 74}) 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 => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@7, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 79}) end, Data = case Payload_length@1 - gleam@bit_string:byte_size(Rest@8) of 0 -> unmask_data(Rest@8, [Mask1, Mask2, Mask3, Mask4], 0, <<>>); Need -> _assert_subject = (erlang:element(9, Transport))( Socket, Need ), {ok, Needed} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail@8 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@8, module => <<"mist/websocket"/utf8>>, function => <<"frame_from_message"/utf8>>, line => 89}) end, _pipe = Rest@8, _pipe@1 = gleam@bit_string:append(_pipe, Needed), unmask_data(_pipe@1, [Mask1, Mask2, Mask3, Mask4], 0, <<>>) end, _pipe@2 = case Opcode of 1 -> {text_frame, Payload_length@1, Data}; 2 -> {binary_frame, Payload_length@1, Data} end, {ok, _pipe@2}; 8 -> {ok, {close_frame, 0, <<>>}} end.