<commit_before>%%%-------------------------------------------------------------------
%%% @author <NAME> <<EMAIL>>
%%% @copyright (C) 2010 <NAME> <<EMAIL>>
%%% @doc apns4erl connection process
%%% @end
%%%-------------------------------------------------------------------
-module(apns_connection).
-author('<NAME> <<EMAIL>>').

-behaviour(gen_server).

-include("apns.hrl").
-include("localized.hrl").
-include_lib("ssl/src/ssl_int.hrl").

-export([start_link/1, start_link/2, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([send_message/2, stop/1]).

-record(state, {out_socket        :: #sslsocket{},
                in_socket         :: #sslsocket{},
                connection        :: #apns_connection{},
                in_buffer = <<>>  :: binary(),
                out_buffer = <<>> :: binary()}).
-type state() :: #state{}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @doc  Sends a message to apple through the connection
-spec send_message(apns:conn_id(), #apns_msg{}) -> ok.
send_message(ConnId, Msg) ->
  gen_server:cast(ConnId, Msg).

%% @doc  Stops the connection
-spec stop(apns:conn_id()) -> ok.
stop(ConnId) ->
  gen_server:cast(ConnId, stop).

%% @hidden
-spec start_link(atom(), #apns_connection{}) -> {ok, pid()} | {error, {already_started, pid()}}.
start_link(Name, Connection) ->
  gen_server:start_link({local, Name}, ?MODULE, Connection, []).
%% @hidden
-spec start_link(#apns_connection{}) -> {ok, pid()}.
start_link(Connection) ->
  gen_server:start_link(?MODULE, Connection, []).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Server implementation, a.k.a.: callbacks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @hidden
-spec init(#apns_connection{}) -> {ok, state()} | {stop, term()}.
init(Connection) ->
  ok = ssl:seed(Connection#apns_connection.ssl_seed),
  try
    case ssl:connect(
           Connection#apns_connection.apple_host,
           Connection#apns_connection.apple_port,
           [{certfile, filename:absname(Connection#apns_connection.cert_file)},
            {mode, binary} |
              case Connection#apns_connection.key_file of
                undefined -> [];
                KeyFile -> [{keyfile, filename:absname(KeyFile)}]
              end],
           Connection#apns_connection.timeout) of
      {ok, OutSocket} ->
        case ssl:connect(
               Connection#apns_connection.feedback_host,
               Connection#apns_connection.feedback_port,
               [{certfile, filename:absname(Connection#apns_connection.cert_file)},
                {mode, binary}|
                  case Connection#apns_connection.key_file of
                    undefined -> [];
                    KeyFile2 -> [{keyfile, filename:absname(KeyFile2)}]
                  end],
               Connection#apns_connection.timeout) of
          {ok, InSocket} ->
            {ok, #state{out_socket  = OutSocket,
                        in_socket   = InSocket,
                        connection  = Connection}};
          {error, Reason} ->
            {stop, Reason}
        end;
      {error, Reason} ->
        {stop, Reason}
    end
  catch
    _:{error, Reason2} ->
      {stop, Reason2}
  end.

%% @hidden
-spec handle_call(X, reference(), state()) -> {stop, {unknown_request, X}, {unknown_request, X}, state()}.
handle_call(Request, _From, State) ->
  {stop, {unknown_request, Request}, {unknown_request, Request}, State}.

%% @hidden
-spec handle_cast(stop | #apns_msg{}, state()) -> {noreply, state()} | {stop, normal | {error, term()}, state()}.
handle_cast(Msg, State) when is_record(Msg, apns_msg) ->
  Socket = State#state.out_socket,
  Payload = build_payload([{alert, Msg#apns_msg.alert},
                           {badge, Msg#apns_msg.badge},
                           {sound, Msg#apns_msg.sound}], Msg#apns_msg.extra),
  BinToken = hexstr_to_bin(Msg#apns_msg.device_token),
  case send_payload(Socket, Msg#apns_msg.id, Msg#apns_msg.expiry, BinToken, Payload) of
    ok ->
      {noreply, State};
    {error, Reason} ->
      {stop, {error, Reason}, State}
  end;
handle_cast(stop, State) ->
  {stop, normal, State}.

%% @hidden
-spec handle_info({ssl, #sslsocket{}, binary()} | {ssl_closed, #sslsocket{}} | X, state()) -> {noreply, state()} | {stop, ssl_closed | {unknown_request, X}, state()}.
handle_info({ssl, SslSocket, Data}, State = #state{out_socket = SslSocket,
                                                   connection =
                                                     #apns_connection{error_fun = Error},
                                                   out_buffer = CurrentBuffer}) ->
  case <<CurrentBuffer/binary, Data/binary>> of
    <<Command:1/unit:8, StatusCode:1/unit:8, MsgId:4/binary, Rest/binary>> ->
      case Command of
        8 -> %% Error
          Status = parse_status(StatusCode),
          try Error(MsgId, Status) of
            stop -> throw({stop, {msg_error, MsgId, Status}, State});
            _ -> noop
          catch
            _:ErrorResult ->
              error_logger:error_msg("Error trying to inform error (~p) msg ~p:~n\t~p~n",
                                     [Status, MsgId, ErrorResult])
          end,
          case erlang:size(Rest) of
            0 -> {noreply, State#state{out_buffer = <<>>}}; %% It was a whole package
            _ -> handle_info({ssl, SslSocket, Rest}, State#state{out_buffer = <<>>})
          end;
        Command ->
          throw({stop, {unknown_command, Command}, State})
      end;
    NextBuffer -> %% We need to wait for the rest of the message
      {noreply, State#state{out_buffer = NextBuffer}}
  end;  
handle_info({ssl, SslSocket, Data}, State = #state{in_socket  = SslSocket,
                                                   connection =
                                                     #apns_connection{feedback_fun = Feedback},
                                                   in_buffer  = CurrentBuffer
                                                  }) ->
  case <<CurrentBuffer/binary, Data/binary>> of
    <<_TimeT:4/big-unsigned-integer-unit:8,
      Length:2/big-unsigned-integer-unit:8,
      Token:Length/binary,
      Rest/binary>> ->
      try Feedback(binary_to_list(Token))
      catch
        _:Error ->
          error_logger:error_msg("Error trying to inform feedback token ~p:~n\t~p~n", [Token, Error])
      end,
      case erlang:size(Rest) of
        0 -> {noreply, State#state{in_buffer = <<>>}}; %% It was a whole package
        _ -> handle_info({ssl, SslSocket, Rest}, State#state{in_buffer = <<>>})
      end;
    NextBuffer -> %% We need to wait for the rest of the message
      {noreply, State#state{in_buffer = NextBuffer}}
  end;
handle_info({ssl_closed, SslSocket}, State = #state{in_socket = SslSocket,
                                                    connection= Connection}) ->
  error_logger:info_msg("Feedback server disconnected. Waiting ~p millis to connect again...~n",
                        [Connection#apns_connection.feedback_timeout]),
  _Timer = erlang:send_after(Connection#apns_connection.feedback_timeout, self(), reconnect),
  {noreply, State#state{in_socket = undefined}};
handle_info(reconnect, State = #state{connection = Connection}) ->
  error_logger:info_msg("Reconnecting the Feedback server...~n"),
  case ssl:connect(
         Connection#apns_connection.feedback_host,
         Connection#apns_connection.feedback_port,
         [{certfile, filename:absname(Connection#apns_connection.cert_file)},
          {ssl_imp, old}, {mode, binary} |
            case Connection#apns_connection.key_file of
              undefined -> [];
              KeyFile -> [{keyfile, filename:absname(KeyFile)}]
            end],
         Connection#apns_connection.timeout) of
    {ok, InSocket} ->
      {noreply, State#state{in_socket = InSocket}};
    {error, Reason} ->
      {stop, {in_closed, Reason}, State}
  end;
handle_info({ssl_closed, SslSocket}, State = #state{out_socket = SslSocket}) ->
  {stop, normal, State};
handle_info(Request, State) ->
  {stop, {unknown_request, Request}, State}.

%% @hidden
-spec terminate(term(), state()) -> ok.
terminate(_Reason, _State) -> ok.

%% @hidden
-spec code_change(term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->  {ok, State}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
build_payload(Params, Extra) ->
  apns_mochijson2:encode(
    {[{<<"aps">>, do_build_payload(Params, [])} | Extra]}).
do_build_payload([{Key,Value}|Params], Payload) -> 
  case Value of
    Value when is_list(Value) ->
      do_build_payload(Params, [{atom_to_binary(Key, utf8), unicode:characters_to_binary(Value)} | Payload]);
    Value when is_integer(Value) ->
      do_build_payload(Params, [{atom_to_binary(Key, utf8), Value} | Payload]);
    #loc_alert{action = Action,
               args   = Args,
               body   = Body,
               image  = Image,
               key    = LocKey} ->
      Json = {case Body of
                none -> [];
                Body -> [{<<"body">>, unicode:characters_to_binary(Body)}]
              end ++ case Action of
                       none -> [];
                       Action -> [{<<"action-loc-key">>, unicode:characters_to_binary(Action)}]
                     end ++ case Image of
                              none -> [];
                              Image -> [{<<"launch-image">>, unicode:characters_to_binary(Image)}]
                            end ++
                [{<<"loc-key">>, unicode:characters_to_binary(LocKey)},
                 {<<"loc-args">>, lists:map(fun unicode:characters_to_binary/1, Args)}]},
      do_build_payload(Params, [{atom_to_binary(Key, utf8), Json} | Payload]);
    _ ->
      do_build_payload(Params,Payload)
  end;
do_build_payload([], Payload) ->
  {Payload}.

-spec send_payload(#sslsocket{}, binary(), non_neg_integer(), binary(), iolist()) -> ok | {error, term()}.
send_payload(Socket, MsgId, Expiry, BinToken, Payload) -> 
    BinPayload = list_to_binary(Payload),
    PayloadLength = erlang:size(BinPayload),
    Packet = [<<1:8, MsgId/binary, Expiry:4/big-unsigned-integer-unit:8,<commit_msg>updated includes to can run on R14 and R15<commit_after>%%%-------------------------------------------------------------------
%%% @author <NAME> <<EMAIL>>
%%% @copyright (C) 2010 <NAME> <<EMAIL>>
%%% @doc apns4erl connection process
%%% @end
%%%-------------------------------------------------------------------
-module(apns_connection).
-author('<NAME> <<EMAIL>>').

-behaviour(gen_server).

-include("apns.hrl").
-include("localized.hrl").

-export([start_link/1, start_link/2, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([send_message/2, stop/1]).

-record(state, {out_socket        :: tuple(),
                in_socket         :: tuple(),
                connection        :: #apns_connection{},
                in_buffer = <<>>  :: binary(),
                out_buffer = <<>> :: binary()}).
-type state() :: #state{}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @doc  Sends a message to apple through the connection
-spec send_message(apns:conn_id(), #apns_msg{}) -> ok.
send_message(ConnId, Msg) ->
  gen_server:cast(ConnId, Msg).

%% @doc  Stops the connection
-spec stop(apns:conn_id()) -> ok.
stop(ConnId) ->
  gen_server:cast(ConnId, stop).

%% @hidden
-spec start_link(atom(), #apns_connection{}) -> {ok, pid()} | {error, {already_started, pid()}}.
start_link(Name, Connection) ->
  gen_server:start_link({local, Name}, ?MODULE, Connection, []).
%% @hidden
-spec start_link(#apns_connection{}) -> {ok, pid()}.
start_link(Connection) ->
  gen_server:start_link(?MODULE, Connection, []).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Server implementation, a.k.a.: callbacks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @hidden
-spec init(#apns_connection{}) -> {ok, state()} | {stop, term()}.
init(Connection) ->
  ok = ssl:seed(Connection#apns_connection.ssl_seed),
  try
    case ssl:connect(
           Connection#apns_connection.apple_host,
           Connection#apns_connection.apple_port,
           [{certfile, filename:absname(Connection#apns_connection.cert_file)},
            {mode, binary} |
              case Connection#apns_connection.key_file of
                undefined -> [];
                KeyFile -> [{keyfile, filename:absname(KeyFile)}]
              end],
           Connection#apns_connection.timeout) of
      {ok, OutSocket} ->
        case ssl:connect(
               Connection#apns_connection.feedback_host,
               Connection#apns_connection.feedback_port,
               [{certfile, filename:absname(Connection#apns_connection.cert_file)},
                {mode, binary}|
                  case Connection#apns_connection.key_file of
                    undefined -> [];
                    KeyFile2 -> [{keyfile, filename:absname(KeyFile2)}]
                  end],
               Connection#apns_connection.timeout) of
          {ok, InSocket} ->
            {ok, #state{out_socket  = OutSocket,
                        in_socket   = InSocket,
                        connection  = Connection}};
          {error, Reason} ->
            {stop, Reason}
        end;
      {error, Reason} ->
        {stop, Reason}
    end
  catch
    _:{error, Reason2} ->
      {stop, Reason2}
  end.

%% @hidden
-spec handle_call(X, reference(), state()) -> {stop, {unknown_request, X}, {unknown_request, X}, state()}.
handle_call(Request, _From, State) ->
  {stop, {unknown_request, Request}, {unknown_request, Request}, State}.

%% @hidden
-spec handle_cast(stop | #apns_msg{}, state()) -> {noreply, state()} | {stop, normal | {error, term()}, state()}.
handle_cast(Msg, State) when is_record(Msg, apns_msg) ->
  Socket = State#state.out_socket,
  Payload = build_payload([{alert, Msg#apns_msg.alert},
                           {badge, Msg#apns_msg.badge},
                           {sound, Msg#apns_msg.sound}], Msg#apns_msg.extra),
  BinToken = hexstr_to_bin(Msg#apns_msg.device_token),
  case send_payload(Socket, Msg#apns_msg.id, Msg#apns_msg.expiry, BinToken, Payload) of
    ok ->
      {noreply, State};
    {error, Reason} ->
      {stop, {error, Reason}, State}
  end;
handle_cast(stop, State) ->
  {stop, normal, State}.

%% @hidden
-spec handle_info({ssl, tuple(), binary()} | {ssl_closed, tuple()} | X, state()) -> {noreply, state()} | {stop, ssl_closed | {unknown_request, X}, state()}.
handle_info({ssl, SslSocket, Data}, State = #state{out_socket = SslSocket,
                                                   connection =
                                                     #apns_connection{error_fun = Error},
                                                   out_buffer = CurrentBuffer}) ->
  case <<CurrentBuffer/binary, Data/binary>> of
    <<Command:1/unit:8, StatusCode:1/unit:8, MsgId:4/binary, Rest/binary>> ->
      case Command of
        8 -> %% Error
          Status = parse_status(StatusCode),
          try Error(MsgId, Status) of
            stop -> throw({stop, {msg_error, MsgId, Status}, State});
            _ -> noop
          catch
            _:ErrorResult ->
              error_logger:error_msg("Error trying to inform error (~p) msg ~p:~n\t~p~n",
                                     [Status, MsgId, ErrorResult])
          end,
          case erlang:size(Rest) of
            0 -> {noreply, State#state{out_buffer = <<>>}}; %% It was a whole package
            _ -> handle_info({ssl, SslSocket, Rest}, State#state{out_buffer = <<>>})
          end;
        Command ->
          throw({stop, {unknown_command, Command}, State})
      end;
    NextBuffer -> %% We need to wait for the rest of the message
      {noreply, State#state{out_buffer = NextBuffer}}
  end;  
handle_info({ssl, SslSocket, Data}, State = #state{in_socket  = SslSocket,
                                                   connection =
                                                     #apns_connection{feedback_fun = Feedback},
                                                   in_buffer  = CurrentBuffer
                                                  }) ->
  case <<CurrentBuffer/binary, Data/binary>> of
    <<_TimeT:4/big-unsigned-integer-unit:8,
      Length:2/big-unsigned-integer-unit:8,
      Token:Length/binary,
      Rest/binary>> ->
      try Feedback(binary_to_list(Token))
      catch
        _:Error ->
          error_logger:error_msg("Error trying to inform feedback token ~p:~n\t~p~n", [Token, Error])
      end,
      case erlang:size(Rest) of
        0 -> {noreply, State#state{in_buffer = <<>>}}; %% It was a whole package
        _ -> handle_info({ssl, SslSocket, Rest}, State#state{in_buffer = <<>>})
      end;
    NextBuffer -> %% We need to wait for the rest of the message
      {noreply, State#state{in_buffer = NextBuffer}}
  end;
handle_info({ssl_closed, SslSocket}, State = #state{in_socket = SslSocket,
                                                    connection= Connection}) ->
  error_logger:info_msg("Feedback server disconnected. Waiting ~p millis to connect again...~n",
                        [Connection#apns_connection.feedback_timeout]),
  _Timer = erlang:send_after(Connection#apns_connection.feedback_timeout, self(), reconnect),
  {noreply, State#state{in_socket = undefined}};
handle_info(reconnect, State = #state{connection = Connection}) ->
  error_logger:info_msg("Reconnecting the Feedback server...~n"),
  case ssl:connect(
         Connection#apns_connection.feedback_host,
         Connection#apns_connection.feedback_port,
         [{certfile, filename:absname(Connection#apns_connection.cert_file)},
          {ssl_imp, old}, {mode, binary} |
            case Connection#apns_connection.key_file of
              undefined -> [];
              KeyFile -> [{keyfile, filename:absname(KeyFile)}]
            end],
         Connection#apns_connection.timeout) of
    {ok, InSocket} ->
      {noreply, State#state{in_socket = InSocket}};
    {error, Reason} ->
      {stop, {in_closed, Reason}, State}
  end;
handle_info({ssl_closed, SslSocket}, State = #state{out_socket = SslSocket}) ->
  {stop, normal, State};
handle_info(Request, State) ->
  {stop, {unknown_request, Request}, State}.

%% @hidden
-spec terminate(term(), state()) -> ok.
terminate(_Reason, _State) -> ok.

%% @hidden
-spec code_change(term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->  {ok, State}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
build_payload(Params, Extra) ->
  apns_mochijson2:encode(
    {[{<<"aps">>, do_build_payload(Params, [])} | Extra]}).
do_build_payload([{Key,Value}|Params], Payload) -> 
  case Value of
    Value when is_list(Value) ->
      do_build_payload(Params, [{atom_to_binary(Key, utf8), unicode:characters_to_binary(Value)} | Payload]);
    Value when is_integer(Value) ->
      do_build_payload(Params, [{atom_to_binary(Key, utf8), Value} | Payload]);
    #loc_alert{action = Action,
               args   = Args,
               body   = Body,
               image  = Image,
               key    = LocKey} ->
      Json = {case Body of
                none -> [];
                Body -> [{<<"body">>, unicode:characters_to_binary(Body)}]
              end ++ case Action of
                       none -> [];
                       Action -> [{<<"action-loc-key">>, unicode:characters_to_binary(Action)}]
                     end ++ case Image of
                              none -> [];
                              Image -> [{<<"launch-image">>, unicode:characters_to_binary(Image)}]
                            end ++
                [{<<"loc-key">>, unicode:characters_to_binary(LocKey)},
                 {<<"loc-args">>, lists:map(fun unicode:characters_to_binary/1, Args)}]},
      do_build_payload(Params, [{atom_to_binary(Key, utf8), Json} | Payload]);
    _ ->
      do_build_payload(Params,Payload)
  end;
do_build_payload([], Payload) ->
  {Payload}.

-spec send_payload(tuple(), binary(), non_neg_integer(), binary(), iolist()) -> ok | {error, term()}.
send_payload(Socket, MsgId, Expiry, BinToken, Payload) -> 
    BinPayload = list_to_binary(Payload),
    PayloadLength = erlang:size(BinPayload),
    Packet = [<<1:8, MsgId/binary, Expiry:4/big-unsigned-integer-unit:8,
{'fraction_non_alphanumeric': 0.21064520683326274, 'fraction_numerical': 0.003953127205986164, 'mean_word_length': 2.4876087313310355, 'pattern_counts': {'":': 0, '<': 89, '<?xml version=': 0, '>': 225, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 34, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3675843', 'n_tokens_mistral': 6254, 'n_tokens_neox': 5997, 'n_words': 1234}