From 8ca1c1b50711767f4752506a2bd7d1f1f65e936d Mon Sep 17 00:00:00 2001 From: Andriy Sedinin Date: Wed, 13 Jul 2022 16:30:21 +0300 Subject: [PATCH 1/9] async streams, max streams from settings http/2 frame --- rebar.config | 2 +- rebar.lock | 6 +- src/apns_connection.erl | 243 ++++++++++++++++++++++++++++++++++------ 3 files changed, 215 insertions(+), 36 deletions(-) diff --git a/rebar.config b/rebar.config index 58a2d43..9403320 100644 --- a/rebar.config +++ b/rebar.config @@ -24,7 +24,7 @@ {deps, [ {gun, "1.3.3"}, - {jsx, "3.0.0"}, + {jsx, "3.1.0"}, {base64url, "1.0.1"} ]}. diff --git a/rebar.lock b/rebar.lock index be37c6e..f3e6eb0 100644 --- a/rebar.lock +++ b/rebar.lock @@ -2,16 +2,16 @@ [{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},0}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.7.3">>},1}, {<<"gun">>,{pkg,<<"gun">>,<<"1.3.3">>},0}, - {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},0}]}. + {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}]}. [ {pkg_hash,[ {<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>}, {<<"cowlib">>, <<"A7FFCD0917E6D50B4D5FB28E9E2085A0CEB3C97DEA310505F7460FF5ED764CE9">>}, {<<"gun">>, <<"CF8B51BEB36C22B9C8DF1921E3F2BC4D2B1F68B49AD4FBC64E91875AA14E16B4">>}, - {<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>}]}, + {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}]}, {pkg_hash_ext,[ {<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>}, {<<"cowlib">>, <<"1E1A3D176D52DAEBBECBBCDFD27C27726076567905C2A9D7398C54DA9D225761">>}, {<<"gun">>, <<"3106CE167F9C9723F849E4FB54EA4A4D814E3996AE243A1C828B256E749041E0">>}, - {<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>}]} + {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}]} ]. diff --git a/src/apns_connection.erl b/src/apns_connection.erl index 75fea77..90937ac 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -54,6 +54,9 @@ , code_change/4 ]). +%% for spawn/3 +-export([ reply_errors_and_cancel_timers/2 ]). + -export_type([ name/0 , host/0 , port/0 @@ -89,8 +92,18 @@ , proxy_info => proxy_info() }. +-type stream_data() :: #{ from := {pid(), term()} + , stream := gun:stream_ref() + , timer := reference() + , status := non_neg_integer() + , headers := gun:headers() + , body := binary() + }. + -type state() :: #{ connection := connection() , gun_pid => pid() + , gun_streams => #{gun:stream_ref() => stream_data()} + , max_gun_streams := non_neg_integer() , gun_monitor => reference() , gun_connect_ref => reference() , client := pid() @@ -207,6 +220,8 @@ callback_mode() -> state_functions. init({Connection, Client}) -> StateData = #{ connection => Connection , client => Client + , gun_streams => #{} + , max_gun_streams => default_max_gun_streams(Connection) , backoff => 1 , backoff_ceiling => application:get_env(apns, backoff_ceiling, 10) }, @@ -231,7 +246,7 @@ open_origin(internal, _, #{connection := Connection} = StateData) -> {next_event, internal, { Host , Port , #{ protocols => [http2] - , transport_opts => TransportOpts + , http2_opts => TransportOpts , retry => 0 }}}}. @@ -304,27 +319,153 @@ await_tunnel_up(EventType, EventContent, StateData) -> connected(internal, on_connect, #{client := Client}) -> Client ! {connection_up, self()}, keep_state_and_data; -connected( {call, {Client, _} = From} +connected( {call, From} , {push_notification, DeviceId, Notification, Headers} - , #{client := Client} = StateData) -> - #{connection := Connection, gun_pid := GunPid} = StateData, - #{timeout := Timeout} = Connection, - Response = push(GunPid, DeviceId, Headers, Notification, Timeout), - {keep_state_and_data, {reply, From, Response}}; -connected( {call, {Client, _} = From} + , StateData) -> + #{ connection := Connection + , gun_pid := GunPid + , gun_streams := Streams0 + , max_gun_streams := MaxStreams} = StateData, + StreamAllowed = stream_allowed(maps:size(Streams0), MaxStreams), + if + not StreamAllowed -> + {keep_state_and_data, {reply, From, {error, {overload, maps:size(Streams0), MaxStreams}}}}; + true -> + #{timeout := Timeout} = Connection, + StreamRef = send_push(GunPid, DeviceId, Headers, Notification), + Tmr = erlang:send_after(Timeout, self(), {timeout, GunPid, StreamRef}), + StreamData = #{ from => From + , stream => StreamRef + , timer => Tmr + , status => 200 %% b4 we know real status + , headers => [] + , body => <<>> }, + Streams1 = Streams0#{StreamRef => StreamData}, + {keep_state, StateData#{gun_streams => Streams1}} + end; +connected( {call, From} , {push_notification, Token, DeviceId, Notification, Headers0} - , #{client := Client} = StateData) -> - #{connection := Connection, gun_pid := GunConn} = StateData, - #{timeout := Timeout} = Connection, - Headers = add_authorization_header(Headers0, Token), - Response = push(GunConn, DeviceId, Headers, Notification, Timeout), - {keep_state_and_data, {reply, From, Response}}; -connected({call, From}, Event, _) when element(1, Event) =:= push_notification -> - {keep_state_and_data, {reply, From, {error, not_connection_owner}}}; + , StateData0) -> + #{ connection := Connection + , gun_pid := GunPid + , gun_streams := Streams0 + , max_gun_streams := MaxStreams} = StateData0, + StreamAllowed = stream_allowed(maps:size(Streams0), MaxStreams), + if + not StreamAllowed -> + {keep_state_and_data, {reply, From, {error, {overload, maps:size(Streams0), MaxStreams}}}}; + true -> + #{timeout := Timeout} = Connection, + Headers = add_authorization_header(Headers0, Token), + StreamRef = send_push(GunPid, DeviceId, Headers, Notification), + Tmr = erlang:send_after(Timeout, self(), {timeout, GunPid, StreamRef}), + StreamData = #{ from => From + , stream => StreamRef + , timer => Tmr + , status => 200 %% b4 we know real status + , headers => [] + , body => <<>> }, + Streams1 = Streams0#{StreamRef => StreamData}, + {keep_state, StateData0#{gun_streams => Streams1}} + end; connected({call, From}, wait_apns_connection_up, _) -> {keep_state_and_data, {reply, From, ok}}; connected({call, From}, Event, _) when Event =/= gun_pid -> {keep_state_and_data, {reply, From, {error, bad_call}}}; +connected( info + , {gun_response, GunPid, StreamRef, fin, Status, Headers} + , #{gun_pid := GunPid} = StateData0) -> + %% got response without body + #{gun_streams := Streams0} = StateData0, + #{StreamRef := StreamData} = Streams0, + #{from := From} = StreamData, + Streams1 = maps:remove(StreamRef, Streams0), + gun:cancel(GunPid, StreamRef), %% final response, closing stream + gen_statem:reply(From, {Status, Headers, no_body}), + {keep_state, StateData0#{gun_streams => Streams1}}; +connected( info + , {gun_response, GunPid, StreamRef, nofin, Status, Headers} + , #{gun_pid := GunPid} = StateData0) -> + %% update status & headers + #{gun_streams := Streams0} = StateData0, + #{StreamRef := StreamState0} = Streams0, + StreamState1 = StreamState0#{status => Status, headers => Headers}, + Streams1 = Streams0#{StreamRef => StreamState1}, + {keep_state, StateData0#{gun_streams => Streams1}}; +connected( info + , {gun_data, GunPid, StreamRef, fin, Data} + , #{gun_pid := GunPid} = StateData0) -> + %% got data, finally + #{gun_streams := Streams0} = StateData0, + #{StreamRef := StreamData} = Streams0, + #{from := From, status := Status, headers := H, body := B0} = StreamData, + Streams1 = maps:remove(StreamRef, Streams0), + gun:cancel(GunPid, StreamRef), %% final, closing stream + gen_statem:reply(From, {Status, H, <>}), + {keep_state, StateData0#{gun_streams => Streams1}}; +connected( info + , {gun_data, GunPid, StreamRef, nofin, Data} + , #{gun_pid := GunPid} = StateData0) -> + %% add data to buffer, still waiting + #{gun_streams := Streams0} = StateData0, + #{StreamRef := StreamState0} = Streams0, + #{body := B0} = StreamState0, + StreamState1 = StreamState0#{body => <>}, + Streams1 = Streams0#{StreamRef => StreamState1}, + {keep_state, StateData0#{gun_streams => Streams1}}; +connected( info + , {gun_error, GunPid, StreamRef, Reason} + , #{gun_pid := GunPid} = StateData0) -> + %% answering with error, remove entry + #{gun_streams := Streams0} = StateData0, + case maps:get(StreamRef, Streams0, null) of + null -> + %% nothing todo + {keep_state, StateData0}; + StreamData -> + #{from := From} = StreamData, + gen_statem:reply(From, {error, Reason}), + Streams1 = maps:remove(StreamRef, Streams0), + gun:cancel(GunPid, StreamRef), + {keep_state, StateData0#{gun_streams => Streams1}} + end; +connected( info + , {gun_error, GunPid, Reason} + , #{gun_pid := GunPid} = StateData0) -> + %% answer with error for all streams, remove all entries, going to reconnect + #{gun_streams := Streams} = StateData0, + spawn(apns_connection, reply_errors_and_cancel_timers, [Streams, Reason]), + {next_state, down, StateData0#{gun_streams => #{}}, + {next_event, internal, {down, ?FUNCTION_NAME, Reason}}}; +connected( info + , {timeout, GunPid, StreamRef} + , #{gun_pid := GunPid, gun_streams := Streams0} = StateData0) -> + %% gun pid matches, we have to answer {error, timeout} + case maps:find(StreamRef, Streams0) of + {ok, StreamData} -> + #{from := From} = StreamData, + gen_statem:reply(From, {error, timeout}), + Streams1 = maps:remove(StreamRef, Streams0), + gun:cancel(GunPid, StreamRef), + {keep_state, StateData0#{gun_streams => Streams1}}; + error -> + %% cant find stream data by stream ref? + %% may be just answered and removed, + %% ignoring + {keep_state, StateData0} + end; +connected(info, + {timeout, _GunPid, _StreamRef}, + StateData0) -> + %% timeout from different connection? + %% ignoring + {keep_state, StateData0}; +connected( info + , {gun_notify, GunPid, settings_changed, Settings} + , #{gun_pid := GunPid, max_gun_streams := MaxStreams0} = StateData0) -> + %% settings received, if contains max_concurrent_streams, update it + MaxStreams1 = maps:get(max_concurrent_streams, Settings, MaxStreams0), + {keep_state, StateData0#{max_gun_streams => MaxStreams1}}; connected(EventType, EventContent, StateData) -> handle_common(EventType, EventContent, ?FUNCTION_NAME, StateData, drop). @@ -356,9 +497,12 @@ handle_common(cast, stop, _, _, _) -> handle_common( info , {'DOWN', GunMon, process, GunPid, Reason} , StateName - , #{gun_pid := GunPid, gun_monitor := GunMon} = StateData + , #{gun_pid := GunPid, gun_monitor := GunMon} = StateData0 , _) -> - {next_state, down, StateData, + %% gun died, answering with errors, cleanup entries + #{gun_streams := Streams} = StateData0, + spawn(apns_connection, reply_errors_and_cancel_timers, [Streams, Reason]), + {next_state, down, StateData0#{gun_streams => #{}}, {next_event, internal, {down, StateName, Reason}}}; handle_common( state_timeout , EventContent @@ -423,24 +567,43 @@ proxy(#{proxy_info := Proxy}) -> proxy(_) -> undefined. +-spec default_max_gun_streams(connection()) -> non_neg_integer() | infinity. +default_max_gun_streams(Setts) -> + case type(Setts) of + token -> 1; %% at start, for token we should set 1 + _ -> 100 + end. + + transport_opts(Connection) -> case type(Connection) of certdata -> Cert = certdata(Connection), Key = keydata(Connection), + %% XXX: why is proplist here? [{cert, Cert}, {key, Key}]; cert -> Certfile = certfile(Connection), Keyfile = keyfile(Connection), + %% XXX: why is proplist here? [{certfile, Certfile}, {keyfile, Keyfile}]; token -> - [] + %% we need to know settings, http2 opt + #{notify_settings_changed => true} end. %%%=================================================================== %%% Internal Functions %%%=================================================================== +-spec(stream_allowed(StreamsCount :: non_neg_integer(), + MaxStreams :: non_neg_integer() | infinity) -> + boolean()). +stream_allowed(_StreamsCount, infinity) -> true; +stream_allowed(StreamsCount, MaxStreams) -> + StreamsCount < MaxStreams. + + -spec get_headers(apns:headers()) -> list(). get_headers(Headers) -> List = [ {<<"apns-id">>, apns_id} @@ -467,21 +630,22 @@ get_device_path(DeviceId) -> add_authorization_header(Headers, Token) -> Headers#{apns_auth_token => <<"bearer ", Token/binary>>}. --spec push(pid(), apns:device_id(), apns:headers(), notification(), integer()) -> - apns:stream_id(). -push(GunConn, DeviceId, HeadersMap, Notification, Timeout) -> +-spec send_push(pid(), apns:device_id(), apns:headers(), notification()) -> + gun:stream_ref(). +send_push(GunPid, DeviceId, HeadersMap, Notification) -> Headers = get_headers(HeadersMap), Path = get_device_path(DeviceId), - StreamRef = gun:post(GunConn, Path, Headers, Notification), - case gun:await(GunConn, StreamRef, Timeout) of - {response, fin, Status, ResponseHeaders} -> - {Status, ResponseHeaders, no_body}; - {response, nofin, Status, ResponseHeaders} -> - {ok, Body} = gun:await_body(GunConn, StreamRef, Timeout), - DecodedBody = jsx:decode(Body, [{return_maps, false}]), - {Status, ResponseHeaders, DecodedBody}; - {error, timeout} -> timeout - end. + gun:post(GunPid, Path, Headers, Notification). + + %% case gun:await(GunPid, StreamRef, Timeout) of + %% {response, fin, Status, ResponseHeaders} -> + %% {Status, ResponseHeaders, no_body}; + %% {response, nofin, Status, ResponseHeaders} -> + %% {ok, Body} = gun:await_body(GunPid, StreamRef, Timeout), + %% DecodedBody = jsx:decode(Body, [{return_maps, false}]), + %% {Status, ResponseHeaders, DecodedBody}; + %% {error, timeout} -> timeout + %% end. -spec backoff(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). backoff(N, Ceiling) -> @@ -492,3 +656,18 @@ backoff(N, Ceiling) -> NString = float_to_list(NextN, [{decimals, 0}]), list_to_integer(NString) end. + +%%%=================================================================== +%%% spawn/3 functions +%%%=================================================================== +-spec reply_errors_and_cancel_timers([stream_data()], term()) -> ok. +reply_errors_and_cancel_timers(Streams, Reason) -> + [reply_error_and_cancel_timer(From, Reason, Tmr) || + #{from := From, timer := Tmr} <- maps:values(Streams)], + ok. + +-spec reply_error_and_cancel_timer(From :: {pid(), term()}, Reason :: term(), + Tmr :: reference()) -> ok. +reply_error_and_cancel_timer(From, Reason, Tmr) -> + erlang:cancel_timer(Tmr), + gen_statem:reply(From, {error, Reason}). From 9c39d1dab1c0473a84a42b820dab30eab8233917 Mon Sep 17 00:00:00 2001 From: Andriy Sedinin Date: Wed, 17 Apr 2024 14:45:39 +0300 Subject: [PATCH 2/9] fix erlang 26 (tls_option should be verify_none) --- rebar.config | 2 +- src/apns_connection.erl | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 9403320..a18ee12 100644 --- a/rebar.config +++ b/rebar.config @@ -23,7 +23,7 @@ %% == Dependencies == {deps, [ - {gun, "1.3.3"}, + {gun, "2.1.0"}, {jsx, "3.1.0"}, {base64url, "1.0.1"} ]}. diff --git a/src/apns_connection.erl b/src/apns_connection.erl index 90937ac..544aef3 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -242,11 +242,13 @@ open_origin(internal, _, #{connection := Connection} = StateData) -> Host = host(Connection), Port = port(Connection), TransportOpts = transport_opts(Connection), + TlsOpts = tls_opts(Connection), {next_state, open_common, StateData, {next_event, internal, { Host , Port , #{ protocols => [http2] , http2_opts => TransportOpts + , tls_opts => TlsOpts , retry => 0 }}}}. @@ -592,6 +594,17 @@ transport_opts(Connection) -> #{notify_settings_changed => true} end. + +tls_opts(Connection) -> + case type(Connection) of + certdata -> + [{verify, verify_peer}]; + cert -> + [{verify, verify_peer}]; + token -> + [{verify, verify_none}] + end. + %%%=================================================================== %%% Internal Functions %%%=================================================================== From e29a23a1d770e9f5647acefbfa4053f1375e0ee9 Mon Sep 17 00:00:00 2001 From: Andriy Sedinin Date: Fri, 19 Apr 2024 11:44:53 +0300 Subject: [PATCH 3/9] raise gun version to 2.1.0, fix gun options --- rebar.config | 2 +- src/apns_connection.erl | 43 ++++++++++++++++++----------------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/rebar.config b/rebar.config index 9403320..a18ee12 100644 --- a/rebar.config +++ b/rebar.config @@ -23,7 +23,7 @@ %% == Dependencies == {deps, [ - {gun, "1.3.3"}, + {gun, "2.1.0"}, {jsx, "3.1.0"}, {base64url, "1.0.1"} ]}. diff --git a/src/apns_connection.erl b/src/apns_connection.erl index 90937ac..1ec814f 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -241,12 +241,14 @@ open_connection(internal, _, #{connection := Connection} = StateData) -> open_origin(internal, _, #{connection := Connection} = StateData) -> Host = host(Connection), Port = port(Connection), - TransportOpts = transport_opts(Connection), + TlsOpts = tls_opts(Connection), + Http2Opts = http2_opts(), {next_state, open_common, StateData, {next_event, internal, { Host , Port , #{ protocols => [http2] - , http2_opts => TransportOpts + , http2_opts => Http2Opts + , tls_opts => TlsOpts , retry => 0 }}}}. @@ -289,12 +291,14 @@ proxy_connect_to_origin(internal, on_connect, StateData) -> #{connection := Connection, gun_pid := GunPid} = StateData, Host = host(Connection), Port = port(Connection), - TransportOpts = transport_opts(Connection), + TlsOpts = tls_opts(Connection), + Http2Opts = http2_opts(), Destination0 = #{ host => Host , port => Port , protocol => http2 + , http2_opts => Http2Opts , transport => tls - , tls_opts => TransportOpts + , tls_opts => TlsOpts }, Destination = case proxy(Connection) of #{ username := Username, password := Password } -> @@ -569,29 +573,30 @@ proxy(_) -> -spec default_max_gun_streams(connection()) -> non_neg_integer() | infinity. default_max_gun_streams(Setts) -> - case type(Setts) of - token -> 1; %% at start, for token we should set 1 - _ -> 100 - end. - + case type(Setts) of + token -> 1; %% at start, for token we should set 1 + _ -> 100 + end. -transport_opts(Connection) -> +tls_opts(Connection) -> case type(Connection) of certdata -> Cert = certdata(Connection), Key = keydata(Connection), - %% XXX: why is proplist here? + %% proplist here, because it goes to ssl:connect/3 (by gun) [{cert, Cert}, {key, Key}]; cert -> Certfile = certfile(Connection), Keyfile = keyfile(Connection), - %% XXX: why is proplist here? [{certfile, Certfile}, {keyfile, Keyfile}]; token -> - %% we need to know settings, http2 opt - #{notify_settings_changed => true} + [] end. +http2_opts() -> + %% we need to know settings (from APN server), gun expects map + #{notify_settings_changed => true}. + %%%=================================================================== %%% Internal Functions %%%=================================================================== @@ -637,16 +642,6 @@ send_push(GunPid, DeviceId, HeadersMap, Notification) -> Path = get_device_path(DeviceId), gun:post(GunPid, Path, Headers, Notification). - %% case gun:await(GunPid, StreamRef, Timeout) of - %% {response, fin, Status, ResponseHeaders} -> - %% {Status, ResponseHeaders, no_body}; - %% {response, nofin, Status, ResponseHeaders} -> - %% {ok, Body} = gun:await_body(GunPid, StreamRef, Timeout), - %% DecodedBody = jsx:decode(Body, [{return_maps, false}]), - %% {Status, ResponseHeaders, DecodedBody}; - %% {error, timeout} -> timeout - %% end. - -spec backoff(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). backoff(N, Ceiling) -> case (math:pow(2, N) - 1) of From 23a0e47f6632963e2e9bb5d6d8d82b1fc5f41297 Mon Sep 17 00:00:00 2001 From: Andriy Sedinin Date: Mon, 29 Jul 2024 16:20:10 +0300 Subject: [PATCH 4/9] updated rebar.lock for new versions --- rebar.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rebar.lock b/rebar.lock index f3e6eb0..482ea07 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,17 +1,17 @@ {"1.2.0", [{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},0}, - {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.7.3">>},1}, - {<<"gun">>,{pkg,<<"gun">>,<<"1.3.3">>},0}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, + {<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}]}. [ {pkg_hash,[ {<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>}, - {<<"cowlib">>, <<"A7FFCD0917E6D50B4D5FB28E9E2085A0CEB3C97DEA310505F7460FF5ED764CE9">>}, - {<<"gun">>, <<"CF8B51BEB36C22B9C8DF1921E3F2BC4D2B1F68B49AD4FBC64E91875AA14E16B4">>}, + {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, + {<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}]}, {pkg_hash_ext,[ {<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>}, - {<<"cowlib">>, <<"1E1A3D176D52DAEBBECBBCDFD27C27726076567905C2A9D7398C54DA9D225761">>}, - {<<"gun">>, <<"3106CE167F9C9723F849E4FB54EA4A4D814E3996AE243A1C828B256E749041E0">>}, + {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, + {<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}]} ]. From e5284e27db189041d9e6ec785e184f5c5d69e6bf Mon Sep 17 00:00:00 2001 From: Andriy Sedinin Date: Mon, 29 Jul 2024 16:39:32 +0300 Subject: [PATCH 5/9] fix some of the dialyzer warnings --- src/apns_connection.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apns_connection.erl b/src/apns_connection.erl index 1ed872b..c87dec9 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -96,7 +96,7 @@ , stream := gun:stream_ref() , timer := reference() , status := non_neg_integer() - , headers := gun:headers() + , headers := gun:req_headers() , body := binary() }. @@ -635,7 +635,7 @@ get_headers(Headers) -> get_device_path(DeviceId) -> <<"/3/device/", DeviceId/binary>>. --spec add_authorization_header(apns:headers(), apnd:token()) -> apns:headers(). +-spec add_authorization_header(apns:headers(), apns:token()) -> apns:headers(). add_authorization_header(Headers, Token) -> Headers#{apns_auth_token => <<"bearer ", Token/binary>>}. @@ -659,7 +659,7 @@ backoff(N, Ceiling) -> %%%=================================================================== %%% spawn/3 functions %%%=================================================================== --spec reply_errors_and_cancel_timers([stream_data()], term()) -> ok. +-spec reply_errors_and_cancel_timers(map(), term()) -> ok. reply_errors_and_cancel_timers(Streams, Reason) -> [reply_error_and_cancel_timer(From, Reason, Tmr) || #{from := From, timer := Tmr} <- maps:values(Streams)], From 06c5d64dd66b59446e5cae81ad319a6f0cbb8556 Mon Sep 17 00:00:00 2001 From: Dinis Rosario Date: Tue, 30 Jul 2024 09:21:52 +0100 Subject: [PATCH 6/9] Silence dialyzer --- rebar.config | 2 +- src/apns.app.src | 1 + src/apns_connection.erl | 14 ++++++-------- src/apns_feedback.erl | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/rebar.config b/rebar.config index a18ee12..9afce6f 100644 --- a/rebar.config +++ b/rebar.config @@ -18,7 +18,7 @@ debug_info ]}. -{minimum_otp_vsn, "19"}. +{minimum_otp_vsn, "24"}. %% == Dependencies == diff --git a/src/apns.app.src b/src/apns.app.src index 2b4ed65..344e4c7 100644 --- a/src/apns.app.src +++ b/src/apns.app.src @@ -7,6 +7,7 @@ {applications, [ kernel, stdlib, + ssl, jsx, gun, base64url diff --git a/src/apns_connection.erl b/src/apns_connection.erl index c87dec9..476bda4 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -292,11 +292,9 @@ proxy_connect_to_origin(internal, on_connect, StateData) -> Host = host(Connection), Port = port(Connection), TlsOpts = tls_opts(Connection), - Http2Opts = http2_opts(), Destination0 = #{ host => Host , port => Port - , protocol => http2 - , http2_opts => Http2Opts + , protocols => [http2] , transport => tls , tls_opts => TlsOpts }, @@ -423,7 +421,7 @@ connected( info %% answering with error, remove entry #{gun_streams := Streams0} = StateData0, case maps:get(StreamRef, Streams0, null) of - null -> + null -> %% nothing todo {keep_state, StateData0}; StreamData -> @@ -453,12 +451,12 @@ connected( info gun:cancel(GunPid, StreamRef), {keep_state, StateData0#{gun_streams => Streams1}}; error -> - %% cant find stream data by stream ref? + %% cant find stream data by stream ref? %% may be just answered and removed, %% ignoring {keep_state, StateData0} end; -connected(info, +connected(info, {timeout, _GunPid, _StreamRef}, StateData0) -> %% timeout from different connection? @@ -594,7 +592,7 @@ tls_opts(Connection) -> , {keyfile, Keyfile} , {verify, verify_peer} ]; token -> - [ {verify, verify_none} ] + [ {verify, verify_none} ] end. http2_opts() -> @@ -661,7 +659,7 @@ backoff(N, Ceiling) -> %%%=================================================================== -spec reply_errors_and_cancel_timers(map(), term()) -> ok. reply_errors_and_cancel_timers(Streams, Reason) -> - [reply_error_and_cancel_timer(From, Reason, Tmr) || + [reply_error_and_cancel_timer(From, Reason, Tmr) || #{from := From, timer := Tmr} <- maps:values(Streams)], ok. diff --git a/src/apns_feedback.erl b/src/apns_feedback.erl index 846afb6..b354f7d 100644 --- a/src/apns_feedback.erl +++ b/src/apns_feedback.erl @@ -25,7 +25,7 @@ -export_type([feedback/0, feedback_config/0]). -type feedback() :: {calendar:datetime(), string()}. --type socket() :: gen_tcp:socket(). +-type socket() :: ssl:sslsocket(). -type feedback_config() :: #{ host := string() , port := pos_integer() , certfile := string() From cf4dcc837001ebd8745b7cdf613505a6ab504967 Mon Sep 17 00:00:00 2001 From: Dinis Rosario Date: Wed, 31 Jul 2024 12:31:30 +0100 Subject: [PATCH 7/9] Update CI with latest version --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed66436..01bb449 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,14 +13,14 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - otp_vsn: [20, 21, 22, 23] + otp_vsn: [24, 25, 26] os: [ubuntu-latest] steps: - uses: actions/checkout@v2 - - uses: erlef/setup-beam@v1.7.0 + - uses: erlef/setup-beam@v1.18.1 with: otp-version: ${{matrix.otp_vsn}} - rebar3-version: '3.14' + rebar3-version: '3.22.1' - run: rebar3 as test xref - run: rebar3 as test dialyzer - run: rebar3 as test ct From e79b61b25027f03d927db3b029802f1a0ca2211d Mon Sep 17 00:00:00 2001 From: Dinis Rosario Date: Wed, 31 Jul 2024 14:08:33 +0100 Subject: [PATCH 8/9] Fix dialyzer error under test profile --- rebar.config | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rebar.config b/rebar.config index 9afce6f..13ac5c7 100644 --- a/rebar.config +++ b/rebar.config @@ -40,6 +40,13 @@ {katana, "1.0.0"}, {mixer, "1.1.1", {pkg, inaka_mixer}}, {meck, "0.9.2"} + ]}, + {dialyzer, [ + {plt_extra_apps, [ + meck, + katana, + katana_test + ]} ]} ]} ]}. From 3adaba06def31e0d6123264c31d733f851b53ad6 Mon Sep 17 00:00:00 2001 From: Dinis Rosario Date: Wed, 31 Jul 2024 14:09:27 +0100 Subject: [PATCH 9/9] Disable CI ct test Test are broken for a couple of years. Until we fix it we should avoid running it at CI. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01bb449..e728bdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,4 +23,4 @@ jobs: rebar3-version: '3.22.1' - run: rebar3 as test xref - run: rebar3 as test dialyzer - - run: rebar3 as test ct + # - run: rebar3 as test ct