From 6d902412d08c6f1dff9e62b81a73d5680f1b9df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lach?= Date: Tue, 26 Aug 2025 10:56:03 +0200 Subject: [PATCH] mikutel: initial commit --- rebar.config | 7 +++ rebar.lock | 1 + src/mikuivr.erl | 101 ++++++++++++++++++++++++++++++++++ src/mikuphone.app.src | 16 ++++++ src/mikuphone.erl | 16 ++++++ src/mikutel.erl | 123 ++++++++++++++++++++++++++++++++++++++++++ src/mikuvisor.erl | 84 +++++++++++++++++++++++++++++ src/rebar.lock | 1 + 8 files changed, 349 insertions(+) create mode 100644 rebar.config create mode 100644 rebar.lock create mode 100644 src/mikuivr.erl create mode 100644 src/mikuphone.app.src create mode 100644 src/mikuphone.erl create mode 100644 src/mikutel.erl create mode 100644 src/mikuvisor.erl create mode 100644 src/rebar.lock diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..a3c5c96 --- /dev/null +++ b/rebar.config @@ -0,0 +1,7 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{shell, [ + %% {config, "config/sys.config"}, + {apps, [mikuphone]} +]}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/src/mikuivr.erl b/src/mikuivr.erl new file mode 100644 index 0000000..53eb970 --- /dev/null +++ b/src/mikuivr.erl @@ -0,0 +1,101 @@ +-module(mikuivr). +-behaviour(gen_server). + +-export([start_link/1]). +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3, + terminate/2]). + +-record(state, {socket, status, number, callerid}). + +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). + +init([Socket]) -> + gen_server:cast(self(), accept), + {ok, #state{socket=Socket, status=ok, number=""}}. + +handle_cast(accept, State = #state{socket=ListenSocket}) -> + {ok, AcceptSocket} = gen_tcp:accept(ListenSocket), + mikuvisor:start_socket(), + %% send(AcceptSocket, "S,sranie", []), + %% send(AcceptSocket, "H,nasrane", []), + send(AcceptSocket, "G,${CALLERID(num)}", []), + {noreply, State#state{socket=AcceptSocket}}; + +handle_cast(_, State) -> + {noreply, State}. + +handle_info({tcp, Socket, Message}, State) -> + handle_message(Message, State), + {noreply, State}; + +handle_info({tcp_closed, _Socket}, State) -> + {stop, normal, State}; + +handle_info({tcp_error, _Socket}, State) -> + {stop, normal, State}; + +handle_info(E, State) -> + io:fwrite("unexpected: ~p~n", [E]), + {noreply, State}. + +handle_call(_E, _From, State) -> + {noreply, State}. + +terminate(_Reason, _Tab) -> + ok. + +code_change(_OldVersion, Tab, _Extra) -> + {ok, Tab}. + +send(Socket, Str, Args) -> + gen_tcp:send(Socket, io_lib:format(Str++"~n", Args)). + +handle_message(Message, State) -> + case Message of + "G"++Rest -> handle_variable_receive(Rest, State); + "*"++_ -> enable_number_mode(Message, State); + "0"++_ -> handle_digit(Message, State); + "1"++_ -> handle_digit(Message, State); + "2"++_ -> handle_digit(Message, State); + "3"++_ -> handle_digit(Message, State); + "4"++_ -> handle_digit(Message, State); + "5"++_ -> handle_digit(Message, State); + "6"++_ -> handle_digit(Message, State); + "7"++_ -> handle_digit(Message, State); + "8"++_ -> handle_digit(Message, State); + "9"++_ -> handle_digit(Message, State); + "#"++_ -> disable_number_mode(Message, State); + _ -> {noreply, State} + end. + +handle_variable_receive(Variable, State) -> + ["${CALLERID(num)}", CallerID] = strings:lexemes(Variable, "="), + {noreply, State#state{callerid = CallerID}}. + +handle_digit(Message, State = #state{status=get_more_digits}) -> + Digit = strings:slice(Message, 1, 1), + Number = State#state.number, + case length(Number) of + 4 -> disable_number_mode(Message, State); + _ -> {noreply, State#state{number = Number ++ Digit}} + end; + +handle_digit(Message, State = #state{status=ok}) -> + {noreply, State}. + +enable_number_mode(_, State) -> + {noreply, State#state{status=get_more_digits, number=""}}. + +disable_number_mode(_, State) -> + register(State#state.callerid, State#state.number), + {noreply, State#state{status=ok, number=""}}. + +register(CallerID, Number) -> + [Supervisor | _] = mikuvisor:get_ancestors(self()), + {ok, {_, Mikutel, _, _}} = supervisor:which_child(Supervisor, mikutel), + gen_server:cast(Mikutel, {register, CallerID, Number}). diff --git a/src/mikuphone.app.src b/src/mikuphone.app.src new file mode 100644 index 0000000..5249f9e --- /dev/null +++ b/src/mikuphone.app.src @@ -0,0 +1,16 @@ +{application, mikuphone, [ + {description, "The Mikuphone"}, + {vsn, "1.0.0"}, + {registered, []}, + {mod, {mikuphone, [ + [2949, "ommhost", 12622, "user", "password", []] + ]}}, + {applications, [ + kernel, + stdlib + ]}, + {env, []}, + {modules, []}, + {licenses, ["MIT"]}, + {links, []} + ]}. diff --git a/src/mikuphone.erl b/src/mikuphone.erl new file mode 100644 index 0000000..b0804e3 --- /dev/null +++ b/src/mikuphone.erl @@ -0,0 +1,16 @@ +-module(mikuphone). +-behaviour(application). +-export([start/2,stop/1]). + +%% 2949 is port for ivr +%% 12622 port for omm + +start(_StartType, StartArgs) -> + %% mikuvisor:start_link(IvrPort), + ssl:start(), + mikuvisor:start_link(StartArgs). + %% spawn(mikutel, connect, [OMMAddress, OMMPort, Username, Password]). + %% mikutel:start(OMMAddress, OMMPort, OMMTLS, Username, Password). + +stop(_) -> + ok. diff --git a/src/mikutel.erl b/src/mikutel.erl new file mode 100644 index 0000000..34d5ee0 --- /dev/null +++ b/src/mikutel.erl @@ -0,0 +1,123 @@ +-module(mikutel). +-behaviour(gen_server). +-export([init/1, + handle_cast/2, + handle_call/3, + handle_info/2, + code_change/3, + terminate/2, + start_link/1]). + +-record(state, {socket, modulus, exponent}). + +%% TODO: add fallback TCP Connection + +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). + +init([Host, Port, Username, Password, _]) -> + Ciphers = ssl:prepend_cipher_suites([ssl:str_to_suite("AES256-GCM-SHA384")], + ssl:cipher_suites(default, 'tlsv1.2')), + {ok, Socket} = ssl:connect(Host, Port, [{verify, verify_none}, + {versions, ['tlsv1.2']}, + {ciphers, Ciphers}, + {active, true}]), + %% {ok, Socket} = gen_tcp:connect(Host, Port, []), + %% Arbitrary sleeps because something drops my Erlang messages?? + timer:send_interval(60000, ping), + login(Socket, Username, Password), + timer:sleep(100), + set_dect_subscription_mode(Socket, 'Configured'), + timer:sleep(100), + subscribe(Socket, [{'PPUserCnf', [{uid, -1}]}, + {'PPDevCnf', [{ppn, -1}]}]), + {ok, #state{socket=Socket}}. + +handle_cast(_, State) -> + {noreply, State}. + +handle_call(_E, _From, State) -> + {noreply, State}. + +handle_cast({register, CallerID, Number}, State) -> + %% TODO + +handle_info({subscribe, Events}, State) -> + subscribe(State#state.socket, Events); + +handle_info({ssl, Socket, Message}, State) -> + handle_response(Message, State); + +handle_info({ssl_closed, Socket}, State) -> + ssl:close(Socket), + {stop, normal, State}; + +%% handle_info({tcp, Socket, Message}, State) -> +%% handle_response(Message, State); + +%% handle_info({tcp_closed, Socket}, State) -> +%% gen_tcp:close(Socket), +%% {stop, normal, State}; + +handle_info(ping, State = #state{socket=Socket}) -> + Data = {'Ping', [], []}, + send(Socket, Data), + {noreply, State}. + +terminate(_Reason, _Tab) -> + ok. + +code_change(_OldVersion, Tab, _Extra) -> + {ok, Tab}. + +login(Socket, Username, Password) -> + Data = {'Open',[{username, Username}, {password, Password}], []}, + send(Socket, Data). + +set_dect_subscription_mode(Socket, Mode) -> + Data = {'SetDECTSubscriptionMode', [{mode, Mode}], []}, + send(Socket, Data). + +transform_event({Event, Args}) -> + {e, [{cmd, 'On'}, {eventType, Event}] ++ Args, []}. + +subscribe(Socket, Args) -> + Payload = lists:map(fun transform_event/1, Args), + Data = {'Subscribe', [], Payload}, + %% send(Socket, Data). + send(Socket, Data). + +send(Socket, Data) -> + RawPayload = lists:flatten(xmerl:export_simple([Data], xmerl_xml, [{prolog, ""}])), + BinPayload = list_to_binary(RawPayload), + Payload = binary_to_list(<>/binary>>), + ssl:send(Socket, Payload). + %% gen_tcp:send(Socket, Payload). + +handle_response(Message, State) -> + {Element, _} = xmerl_scan:string(Message), + {Response, Parameters, Body} = xmerl_lib:simplify_element(Element), + io:format("Received: ~p~n", [Response]), + io:format("Params: ~p~n", [Parameters]), + io:format("Body: ~p~n", [Body]), + case Response of + 'OpenResp' -> handle_login(Body, Parameters, State); + 'EventPPDevCnf' -> handle_device_configuration_event(Body, Parameters, State); + _ -> {noreply, State} + end. + +handle_login(_, [{publicKey, [{modulus, Modulus}, {exponent, Exponent}], []} | _], State) -> + {noreply, State#state{modulus=Modulus, exponent=Exponent}}; + +handle_login(Body, [_ | Rest], State) -> + handle_login(Body, Rest, State). + +handle_device_configuration_event(_, [{pp, Args, _}], State) -> + handle_device_configuration_event(Args, State). + +handle_device_configuration_event([{ipei, Ipei} | _], State) -> + ok; + %% create_device(). + +handle_device_configuration_event([_ | Rest], State) -> + handle_device_configuration_event(Rest, State). diff --git a/src/mikuvisor.erl b/src/mikuvisor.erl new file mode 100644 index 0000000..ba10771 --- /dev/null +++ b/src/mikuvisor.erl @@ -0,0 +1,84 @@ +-module(mikuvisor). +-behaviour(supervisor). +-export([init/1, start_link/1, start_child/0]). + +start_link([IvrPort, OMMAddress, OMMPort, OMMUsername, OMMPassword, OMMTLS]) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, [IvrPort, OMMAddress, OMMPort, OMMUsername, OMMPassword, OMMTLS]). + +init([IvrPort, OMMAddress, OMMPort, OMMUsername, OMMPassword, OMMTLS]) -> + spawn_link(fun empty_listeners/0), + {ok, IVRSocket} = gen_tcp:listen(IvrPort, []), + %% Flags = #{strategy => one_for_one, + %% intensity => 60, + %% period => 3600}, + %% Mikutel = case OMMTLS of + %% true -> #{id => mikutel, + %% start => {mikutel_ssl, start_link, [[OMMAddress, + %% OMMPort, + %% OMMUsername, + %% OMMPassword, + %% OMMTLS]]}, + %% type => worker, + %% modules => [mikutel_ssl]}; + %% _ -> #{id => mikutel, + %% start => {mikutel_plain, start_link, [[OMMAddress, + %% OMMPort, + %% OMMUsername, + %% OMMPassword, + %% OMMTLS]]}, + %% type => worker, + %% modules => [mikutel_plain]} + %% end, + %% Specs = [#{id => mikuivr, + %% start => {mikuivr, start_link, [[IVRSocket]]}, + %% type => worker, + %% modules => [mikuivr]}, + %% Mikutel], + %% {ok, {Flags, Specs}}. + + {ok, {{one_for_one, 60, 3600}, + [ + { + mikuivr, + {mikuivr, start_link, [[IVRSocket]]}, + temporary, + 1000, + worker, + [mikuivr] + }, + { + mikutel, + {mikutel, start_link, [[OMMAddress, + OMMPort, + OMMUsername, + OMMPassword, + OMMTLS]]}, + temporary, + 1000, + worker, + [mikuivr] + } + ] + }}. + +start_child() -> + supervisor:start_child(?MODULE, []). + +empty_listeners() -> + [start_child() || _ <- lists:seq(1,20)], + ok. + +get_ancestors(PID) when is_pid(PID) -> + case erlang:process_info(PID) of + {dictionary, Dict} -> ancestors_from_dict(Dict); + _ -> [] + end. + +ancestors_from_dict([]) -> + []; + +ancestors_from_dict([{'$ancestors', Ancestors} | _]) -> + Ancestors; + +ancestors_from_dict([_ | Rest]) -> + ancestors_from_dict(Rest). diff --git a/src/rebar.lock b/src/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/src/rebar.lock @@ -0,0 +1 @@ +[].