From 3dbb9f1e29ce49a29a39ea8c8c101cabfc9cba40 Mon Sep 17 00:00:00 2001 From: Mikhail Uvarov Date: Tue, 30 Jul 2024 15:32:58 +0200 Subject: [PATCH 01/23] Test on Erlang 27 Update worker_pool --- .github/workflows/ci.yml | 4 ++-- rebar.config | 6 +++++- rebar.lock | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17965e0..6d06f34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: name: OTP ${{matrix.otp_vsn}} strategy: matrix: - otp_vsn: ['26.2', '25.3', '24.3'] + otp_vsn: ['27.0.1', '26.2', '25.3', '24.3'] rebar_vsn: ['3.22.0'] runs-on: 'ubuntu-22.04' env: @@ -29,4 +29,4 @@ jobs: - run: make test - run: make ct - run: make dialyzer - if: ${{ matrix.otp == '26.2' }} + if: ${{ matrix.otp == '27.0.1' }} diff --git a/rebar.config b/rebar.config index c113214..2d3f11a 100644 --- a/rebar.config +++ b/rebar.config @@ -12,7 +12,7 @@ {bbmustache, "1.12.2"}, {uuid, "2.0.7", {pkg, uuid_erl}}, {gun, "2.0.1"}, - {worker_pool, "6.2.0"}, + {worker_pool, "6.2.1"}, {fast_tls, "1.1.21"}, {fast_scram, "0.6.0"} ]}. @@ -21,6 +21,10 @@ {project_plugins, [rebar3_hex, {rebar3_codecov, "0.6.0"}]}. +{overrides, [ + {override, worker_pool, [{minimum_otp_vsn, "24"}]} +]}. + {relx, [{release, {escalus, "0.0.1"}, [escalus]}, {dev_mode, true}, diff --git a/rebar.lock b/rebar.lock index 385e2db..efb643d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -11,7 +11,7 @@ {<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.26">>},1}, {<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.7">>},1}, {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.7">>},0}, - {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.2.0">>},0}]}. + {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.2.1">>},0}]}. [ {pkg_hash,[ {<<"base16">>, <<"F0549F732E03BE8124ED0D19FD5EE52146CC8BE24C48CBC3F23AB44B157F11A2">>}, @@ -26,7 +26,7 @@ {<<"p1_utils">>, <<"67B0C4AC9FA3BA3EF563B31AA111B0A004439A37FAC85E027F1C3617E1C7EC6C">>}, {<<"quickrand">>, <<"D2BD76676A446E6A058D678444B7FDA1387B813710D1AF6D6E29BB92186C8820">>}, {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}, - {<<"worker_pool">>, <<"506DE38C528A81ED2C6A80A419B83DDE6DA5E295BD320BDF4D35A69AFEB0247A">>}]}, + {<<"worker_pool">>, <<"BD98A0BE1D20057AE9967CBE73D263AEA5BE14BBE4C73CAFEB1378572FF14561">>}]}, {pkg_hash_ext,[ {<<"base16">>, <<"06EA2D48343282E712160BA89F692B471DB8B36ABE8394F3445FF9032251D772">>}, {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, @@ -40,5 +40,5 @@ {<<"p1_utils">>, <<"D0379E8C1156B98BD64F8129C1DE022FCCA4F2FDB7486CE73BF0ED2C3376B04C">>}, {<<"quickrand">>, <<"B8ACBF89A224BC217C3070CA8BEBC6EB236DBE7F9767993B274084EA044D35F0">>}, {<<"uuid">>, <<"4E4C5CA3461DC47C5E157ED42AA3981A053B7A186792AF972A27B14A9489324E">>}, - {<<"worker_pool">>, <<"E1DED160797FBE656AD683109DBD741B520097DF17BDF7B51E3E4697073A6E62">>}]} + {<<"worker_pool">>, <<"64E560DE08CA5E7DB8BD4CDCC7B744B0659696194E3BC9E56239BA4A0F7E24F9">>}]} ]. From 67f424a4bbba23fbeee7074c7ef0f1dbfe7971a8 Mon Sep 17 00:00:00 2001 From: Mikhail Uvarov Date: Tue, 30 Jul 2024 15:58:11 +0200 Subject: [PATCH 02/23] Wait for process to stop in escalus_fresh:stop/0 --- src/escalus_fresh.erl | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/escalus_fresh.erl b/src/escalus_fresh.erl index 1943163..83ca0f4 100644 --- a/src/escalus_fresh.erl +++ b/src/escalus_fresh.erl @@ -121,7 +121,20 @@ start(_Config) -> application:ensure_all_started(worker_pool), ensure_table_present(nasty_global_table()). stop(_) -> - nasty_global_table() ! bye. + case whereis(nasty_global_table()) of + undefined -> + ok; + Pid -> + Mon = erlang:monitor(process, Pid), + Pid ! bye, + receive + {'DOWN', Mon, process, Pid, _Reason} -> + ok + after 5000 -> + error(stop_fresh_timeout) + end + end. + -spec clean() -> no_return() | ok. clean() -> wpool:start_sup_pool(unregister_pool, [{workers, ?UNREGISTER_WORKERS}, From 755e39e9ebbf96403e0eb8d9eca3435a34fc1f23 Mon Sep 17 00:00:00 2001 From: Mikhail Uvarov Date: Tue, 30 Jul 2024 16:04:20 +0200 Subject: [PATCH 03/23] Fix race condition in ensure_table_present --- src/escalus_fresh.erl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/escalus_fresh.erl b/src/escalus_fresh.erl index 83ca0f4..7a89423 100644 --- a/src/escalus_fresh.erl +++ b/src/escalus_fresh.erl @@ -192,12 +192,23 @@ do_delete_users(Conf) -> ensure_table_present(T) -> + Parent = self(), + StartRef = make_ref(), RunDB = fun() -> ets:new(T, [named_table, public]), + Parent ! {started, StartRef}, receive bye -> ok end end, case ets:info(T) of undefined -> - P = spawn(RunDB), - erlang:register(T, P); + {Pid, Mon} = spawn_monitor(RunDB), + receive + {started, StartRef} -> + erlang:register(T, Pid), + erlang:demonitor(Mon); + {'DOWN', Mon, process, Pid, _Reason} -> + ok + after 5000 -> + error(failed_to_start_fresh_table) + end; _nasty_table_is_there_well_run_with_it -> ok end. From 561d8e6cb3954c1aaf8398b8759358d5ec193ca4 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Fri, 23 Aug 2024 11:27:20 +0200 Subject: [PATCH 04/23] Export metadata from connection --- src/escalus_connection.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index 1b385ff..fe450e5 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -63,7 +63,7 @@ | fun((client(), exml_stream:element()) -> boolean()). -export_type([stanza_handler/0]). --export_type([t/0]). +-export_type([t/0, metadata/0]). %% Private -export([connection_step/2]). From 79e19d847f1cf9a0fe1754147315b603ee292e0f Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 19:01:37 +0100 Subject: [PATCH 05/23] Update files chmod flags --- src/escalus_pubsub_stanza.erl | 0 src/escalus_tcp.erl | 0 src/escalus_ws.erl | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/escalus_pubsub_stanza.erl mode change 100755 => 100644 src/escalus_tcp.erl mode change 100755 => 100644 src/escalus_ws.erl diff --git a/src/escalus_pubsub_stanza.erl b/src/escalus_pubsub_stanza.erl old mode 100755 new mode 100644 diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl old mode 100755 new mode 100644 diff --git a/src/escalus_ws.erl b/src/escalus_ws.erl old mode 100755 new mode 100644 From 139984c6e904bed00a5cc7ad65abe543d4ac5602 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 16 Dec 2024 08:44:41 +0100 Subject: [PATCH 06/23] Update GHA versions --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d06f34..13d9e04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,13 +14,13 @@ jobs: name: OTP ${{matrix.otp_vsn}} strategy: matrix: - otp_vsn: ['27.0.1', '26.2', '25.3', '24.3'] - rebar_vsn: ['3.22.0'] - runs-on: 'ubuntu-22.04' + otp_vsn: ['27', '26'] + rebar_vsn: ['3.24.0'] + runs-on: 'ubuntu-24.04' env: OTPVER: ${{ matrix.otp_vsn }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: otp-version: ${{ matrix.otp_vsn }} @@ -29,4 +29,4 @@ jobs: - run: make test - run: make ct - run: make dialyzer - if: ${{ matrix.otp == '27.0.1' }} + if: ${{ matrix.otp == '27' }} From b1cd89bc85dcaa21f23cfbee44b2dbcb81fb480a Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 18:58:19 +0100 Subject: [PATCH 07/23] Use the right time units in system_time --- src/escalus_bosh.erl | 2 +- src/escalus_ws.erl | 2 +- test/connection_SUITE.erl | 2 +- test/escalus_tcp_tests.erl | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/escalus_bosh.erl b/src/escalus_bosh.erl index c359688..8767c56 100644 --- a/src/escalus_bosh.erl +++ b/src/escalus_bosh.erl @@ -406,7 +406,7 @@ handle_info(_, #state{ terminated = true } = S) -> {noreply, S}; handle_info({http_reply, Ref, Body, _Transport} = HttpReply, #state{ pending_replies = PendingReplies } = S0) -> - Timestamp = os:system_time(micro_seconds), + Timestamp = os:system_time(microsecond), {ok, #xmlel{attrs = Attrs} = XmlBody} = exml:parse(Body), NewS = case {queue:peek(S0#state.requests), S0#state.quickfail andalso detect_type(Attrs) == streamend} of diff --git a/src/escalus_ws.erl b/src/escalus_ws.erl index dbd32a4..3c58252 100644 --- a/src/escalus_ws.erl +++ b/src/escalus_ws.erl @@ -325,7 +325,7 @@ do_connect(#{host := Host, port := Port}, TransportOpts) -> handle_data(Data, State = #state{parser = Parser, compress = Compress}) -> - Timestamp = os:system_time(micro_seconds), + Timestamp = os:system_time(microsecond), {ok, NewParser, Stanzas} = case Compress of false -> diff --git a/test/connection_SUITE.erl b/test/connection_SUITE.erl index 267ca54..8c0817b 100644 --- a/test/connection_SUITE.erl +++ b/test/connection_SUITE.erl @@ -150,7 +150,7 @@ msg() -> <<"Message">>. msg2() -> <<"Message 2">>. -metadata() -> #{recv_timestamp => os:system_time(micro_seconds)}. +metadata() -> #{recv_timestamp => os:system_time(microsecond)}. client() -> #client{jid = my_jid(), rcv_pid = self(), props = []}. diff --git a/test/escalus_tcp_tests.erl b/test/escalus_tcp_tests.erl index 33a261d..5f4ad73 100644 --- a/test/escalus_tcp_tests.erl +++ b/test/escalus_tcp_tests.erl @@ -19,9 +19,9 @@ interleave_msgs_and_rs_test() -> State = #state{owner = self(), sm_state = {true, 0, active}, event_client = self()}, - SecondState = escalus_tcp:forward_to_owner(FirstStanzas, State, os:system_time(micro_seconds)), + SecondState = escalus_tcp:forward_to_owner(FirstStanzas, State, os:system_time(microsecond)), #state{sm_state = SMState} = - escalus_tcp:forward_to_owner(SecondStanzas, SecondState, os:system_time(micro_seconds)), + escalus_tcp:forward_to_owner(SecondStanzas, SecondState, os:system_time(microsecond)), ?assertEqual({true, 3, active}, SMState), meck:unload(escalus_event), meck:unload(gen_tcp). From 6d77f5346f63dbb56b5c5fed989ae8cfa8afe488 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 20:09:53 +0100 Subject: [PATCH 08/23] Upgrade minor deps --- rebar.config | 8 ++++---- rebar.lock | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/rebar.config b/rebar.config index 2d3f11a..93551cb 100644 --- a/rebar.config +++ b/rebar.config @@ -3,16 +3,16 @@ {i, [".", "include"]} ]}. -{require_min_otp_vsn, "21"}. +{require_min_otp_vsn, "26"}. {deps, [ {exml, "3.4.1", {pkg, hexml}}, {base16, "2.0.1"}, - {meck, "0.9.2"}, + {meck, "1.0.0"}, {bbmustache, "1.12.2"}, {uuid, "2.0.7", {pkg, uuid_erl}}, - {gun, "2.0.1"}, - {worker_pool, "6.2.1"}, + {gun, "2.1.0"}, + {worker_pool, "6.4.0"}, {fast_tls, "1.1.21"}, {fast_scram, "0.6.0"} ]}. diff --git a/rebar.lock b/rebar.lock index efb643d..bbd8cd6 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,44 +1,44 @@ {"1.2.0", [{<<"base16">>,{pkg,<<"base16">>,<<"2.0.1">>},0}, {<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, - {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.12.1">>},1}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, {<<"exml">>,{pkg,<<"hexml">>,<<"3.4.1">>},0}, {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.5">>},1}, {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.6.0">>},0}, {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.21">>},0}, - {<<"gun">>,{pkg,<<"gun">>,<<"2.0.1">>},0}, - {<<"meck">>,{pkg,<<"meck">>,<<"0.9.2">>},0}, + {<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0}, + {<<"meck">>,{pkg,<<"meck">>,<<"1.0.0">>},0}, {<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.26">>},1}, {<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.7">>},1}, {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.7">>},0}, - {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.2.1">>},0}]}. + {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.4.0">>},0}]}. [ {pkg_hash,[ {<<"base16">>, <<"F0549F732E03BE8124ED0D19FD5EE52146CC8BE24C48CBC3F23AB44B157F11A2">>}, {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, - {<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>}, + {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, {<<"exml">>, <<"9581FE6512D9772C61BBE611CD4A8E5BB90B4D4481275325EC520F7A931A9393">>}, {<<"fast_pbkdf2">>, <<"6045138C4C209FC8222A0B18B2CB1D7BD7407EF4ADAD0F14C5E0F7F4726E3E41">>}, {<<"fast_scram">>, <<"70724F584A118DA147A51EE38DEE56203F217D58AD61E0BB2C2EF834C16B35B8">>}, {<<"fast_tls">>, <<"65D7D547A09EEFB37A1C0D04D8601FAC4F3E6E2C1EDE859A7787081670F9648D">>}, - {<<"gun">>, <<"160A9A5394800FCBA41BC7E6D421295CF9A7894C2252C0678244948E3336AD73">>}, - {<<"meck">>, <<"85CCBAB053F1DB86C7CA240E9FC718170EE5BDA03810A6292B5306BF31BAE5F5">>}, + {<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>}, + {<<"meck">>, <<"24676CB6EE6951530093A93EDCD410CFE4CB59FE89444B875D35C9D3909A15D0">>}, {<<"p1_utils">>, <<"67B0C4AC9FA3BA3EF563B31AA111B0A004439A37FAC85E027F1C3617E1C7EC6C">>}, {<<"quickrand">>, <<"D2BD76676A446E6A058D678444B7FDA1387B813710D1AF6D6E29BB92186C8820">>}, {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}, - {<<"worker_pool">>, <<"BD98A0BE1D20057AE9967CBE73D263AEA5BE14BBE4C73CAFEB1378572FF14561">>}]}, + {<<"worker_pool">>, <<"0347B805A8E5804B5676A9885FB3B9B6C1627099C449C3C67C0E8E6AF79E9AA6">>}]}, {pkg_hash_ext,[ {<<"base16">>, <<"06EA2D48343282E712160BA89F692B471DB8B36ABE8394F3445FF9032251D772">>}, {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, - {<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>}, + {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, {<<"exml">>, <<"D8E7894E2544402B4986EEB2443C15B51B14F686266F091DBF2777D1D99A2FA2">>}, {<<"fast_pbkdf2">>, <<"BC3B5A3CAB47AD114FF8BB815FEDE62A6187ACD14D8B37412F2AF8236A089CEF">>}, {<<"fast_scram">>, <<"771D034341599CFC6A6C5E56CF924B68D2C7478088CAF17419E3147B66914667">>}, {<<"fast_tls">>, <<"131542913937025E48CD80AA81F00359686D5501B75621E72026A87B5229505B">>}, - {<<"gun">>, <<"A10BC8D6096B9502205022334F719CC9A08D9ADCFBFC0DBEE9EF31B56274A20B">>}, - {<<"meck">>, <<"81344F561357DC40A8344AFA53767C32669153355B626EA9FCBC8DA6B3045826">>}, + {<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>}, + {<<"meck">>, <<"680A9BCFE52764350BEB9FB0335FB75FEE8E7329821416CEE0A19FEC35433882">>}, {<<"p1_utils">>, <<"D0379E8C1156B98BD64F8129C1DE022FCCA4F2FDB7486CE73BF0ED2C3376B04C">>}, {<<"quickrand">>, <<"B8ACBF89A224BC217C3070CA8BEBC6EB236DBE7F9767993B274084EA044D35F0">>}, {<<"uuid">>, <<"4E4C5CA3461DC47C5E157ED42AA3981A053B7A186792AF972A27B14A9489324E">>}, - {<<"worker_pool">>, <<"64E560DE08CA5E7DB8BD4CDCC7B744B0659696194E3BC9E56239BA4A0F7E24F9">>}]} + {<<"worker_pool">>, <<"59946FBCE1D331CDEB153EDD36A823DC1AAB4C2482662582B983C9C90EBC3461">>}]} ]. From 8e0f9da93c4c8580b635fdab54d7fd5893f244b0 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 20:10:03 +0100 Subject: [PATCH 09/23] Update release versioning mechanism to git --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 93551cb..e22a145 100644 --- a/rebar.config +++ b/rebar.config @@ -25,7 +25,7 @@ {override, worker_pool, [{minimum_otp_vsn, "24"}]} ]}. -{relx, [{release, {escalus, "0.0.1"}, +{relx, [{release, {escalus, git}, [escalus]}, {dev_mode, true}, {sys_config, "./sys.config"}, From 22311dbd0266152acc3c6d154f7b0edb6cbcc0bf Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 18:41:44 +0100 Subject: [PATCH 10/23] Remove base16 library --- rebar.config | 1 - rebar.lock | 5 +---- src/escalus.app.src | 1 - src/escalus_auth.erl | 4 ++-- src/escalus_component.erl | 2 +- src/escalus_stanza.erl | 2 +- 6 files changed, 5 insertions(+), 10 deletions(-) diff --git a/rebar.config b/rebar.config index e22a145..8b1a562 100644 --- a/rebar.config +++ b/rebar.config @@ -7,7 +7,6 @@ {deps, [ {exml, "3.4.1", {pkg, hexml}}, - {base16, "2.0.1"}, {meck, "1.0.0"}, {bbmustache, "1.12.2"}, {uuid, "2.0.7", {pkg, uuid_erl}}, diff --git a/rebar.lock b/rebar.lock index bbd8cd6..64b2aad 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,6 +1,5 @@ {"1.2.0", -[{<<"base16">>,{pkg,<<"base16">>,<<"2.0.1">>},0}, - {<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, +[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, {<<"exml">>,{pkg,<<"hexml">>,<<"3.4.1">>},0}, {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.5">>},1}, @@ -14,7 +13,6 @@ {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.4.0">>},0}]}. [ {pkg_hash,[ - {<<"base16">>, <<"F0549F732E03BE8124ED0D19FD5EE52146CC8BE24C48CBC3F23AB44B157F11A2">>}, {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, {<<"exml">>, <<"9581FE6512D9772C61BBE611CD4A8E5BB90B4D4481275325EC520F7A931A9393">>}, @@ -28,7 +26,6 @@ {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}, {<<"worker_pool">>, <<"0347B805A8E5804B5676A9885FB3B9B6C1627099C449C3C67C0E8E6AF79E9AA6">>}]}, {pkg_hash_ext,[ - {<<"base16">>, <<"06EA2D48343282E712160BA89F692B471DB8B36ABE8394F3445FF9032251D772">>}, {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, {<<"exml">>, <<"D8E7894E2544402B4986EEB2443C15B51B14F686266F091DBF2777D1D99A2FA2">>}, diff --git a/src/escalus.app.src b/src/escalus.app.src index b7af277..e43c97e 100644 --- a/src/escalus.app.src +++ b/src/escalus.app.src @@ -8,7 +8,6 @@ stdlib, ssl, exml, - base16, gun, meck, bbmustache, diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index 8072eaf..a0217ca 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -191,7 +191,7 @@ md5_digest_response(ChallengeData, Props) -> Server = get_property(server, Props), Resource = get_property(resource, Props), Nonce = get_property(<<"nonce">>, ChallengeData), - CNonce = base16:encode(crypto:strong_rand_bytes(16)), + CNonce = binary:encode_hex(crypto:strong_rand_bytes(16), lowercase), Realm = proplists:get_value(<<"realm">>, ChallengeData, <<>>), QOP = get_property(<<"qop">>, ChallengeData), NC = <<"00000001">>, @@ -230,7 +230,7 @@ scram_sha_auth_payload(fast_tls, tls_unique, Conn) -> hex_md5(Data) -> - base16:encode(crypto:hash(md5, Data)). + binary:encode_hex(crypto:hash(md5, Data), lowercase). %%-------------------------------------------------------------------- %% Helpers - actions diff --git a/src/escalus_component.erl b/src/escalus_component.erl index b1225e4..9ee4478 100644 --- a/src/escalus_component.erl +++ b/src/escalus_component.erl @@ -214,4 +214,4 @@ component_stream_start(Component) -> component_handshake_el(SID, Password) -> Handshake = crypto:hash(sha, <>), #xmlel{name = <<"handshake">>, - children = [#xmlcdata{content = base16:encode(Handshake)}]}. \ No newline at end of file + children = [#xmlcdata{content = binary:encode_hex(Handshake, lowercase)}]}. diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index e31fc2a..0f9c53f 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -847,7 +847,7 @@ marker_el(MarkerName, MessageId) when MarkerName =:= <<"received">> orelse -spec id() -> binary(). id() -> - base16:encode(crypto:strong_rand_bytes(16)). + binary:encode_hex(crypto:strong_rand_bytes(16), lowercase). -spec uuid_v4() -> binary(). uuid_v4() -> From 2850a21db326562fe87773d7539b9acae876a835 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 18:43:08 +0100 Subject: [PATCH 11/23] Update fast_scram --- rebar.config | 2 +- rebar.lock | 12 ++++++------ src/escalus_auth.erl | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rebar.config b/rebar.config index 8b1a562..3ecc346 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {gun, "2.1.0"}, {worker_pool, "6.4.0"}, {fast_tls, "1.1.21"}, - {fast_scram, "0.6.0"} + {fast_scram, "0.6.1"} ]}. %% To override the plugin as installed by worker_pool diff --git a/rebar.lock b/rebar.lock index 64b2aad..7189936 100644 --- a/rebar.lock +++ b/rebar.lock @@ -2,8 +2,8 @@ [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, {<<"exml">>,{pkg,<<"hexml">>,<<"3.4.1">>},0}, - {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.5">>},1}, - {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.6.0">>},0}, + {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.6">>},1}, + {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.6.1">>},0}, {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.21">>},0}, {<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0}, {<<"meck">>,{pkg,<<"meck">>,<<"1.0.0">>},0}, @@ -16,8 +16,8 @@ {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, {<<"exml">>, <<"9581FE6512D9772C61BBE611CD4A8E5BB90B4D4481275325EC520F7A931A9393">>}, - {<<"fast_pbkdf2">>, <<"6045138C4C209FC8222A0B18B2CB1D7BD7407EF4ADAD0F14C5E0F7F4726E3E41">>}, - {<<"fast_scram">>, <<"70724F584A118DA147A51EE38DEE56203F217D58AD61E0BB2C2EF834C16B35B8">>}, + {<<"fast_pbkdf2">>, <<"199BCEC73A1A246941E9465D3DC41052953B638128841ED24B29ED03CF70AF27">>}, + {<<"fast_scram">>, <<"BEEADB03D774640F0671681759CE53B2FF33CB58C86FD9BF2A793E2FC1ED0F5D">>}, {<<"fast_tls">>, <<"65D7D547A09EEFB37A1C0D04D8601FAC4F3E6E2C1EDE859A7787081670F9648D">>}, {<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>}, {<<"meck">>, <<"24676CB6EE6951530093A93EDCD410CFE4CB59FE89444B875D35C9D3909A15D0">>}, @@ -29,8 +29,8 @@ {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, {<<"exml">>, <<"D8E7894E2544402B4986EEB2443C15B51B14F686266F091DBF2777D1D99A2FA2">>}, - {<<"fast_pbkdf2">>, <<"BC3B5A3CAB47AD114FF8BB815FEDE62A6187ACD14D8B37412F2AF8236A089CEF">>}, - {<<"fast_scram">>, <<"771D034341599CFC6A6C5E56CF924B68D2C7478088CAF17419E3147B66914667">>}, + {<<"fast_pbkdf2">>, <<"35EEC22629AAA739915843C7B7DE0D84657D1ECE972D8BBC86368747E9C14012">>}, + {<<"fast_scram">>, <<"FE0650A309FDF97C75E1EA812CCFB40EB464ECAFD3783E83AA17C7F572EDAB0B">>}, {<<"fast_tls">>, <<"131542913937025E48CD80AA81F00359686D5501B75621E72026A87B5229505B">>}, {<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>}, {<<"meck">>, <<"680A9BCFE52764350BEB9FB0335FB75FEE8E7329821416CEE0A19FEC35433882">>}, diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index a0217ca..ab850d5 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -29,7 +29,7 @@ %% Some shorthands -type client() :: escalus_connection:client(). -type user_spec() :: escalus_users:user_spec(). --type hash_type() :: scram:hash_type(). +-type hash_type() :: fast_scram:sha_type(). -type plus_variant() :: none | tls_unique. -type scram_options() :: #{plus_variant := plus_variant(), hash_type := hash_type(), From 21b64f6e1f4f69188b36d6bc03e88146f164cb86 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 7 Jan 2025 20:24:27 +0100 Subject: [PATCH 12/23] Introduce hibernate_after flag --- src/escalus_connection.erl | 4 +++- src/escalus_tcp.erl | 38 +++++++++++++++++++++----------------- src/escalus_ws.erl | 2 +- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index fe450e5..1a69cbf 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -425,7 +425,9 @@ use_zlib(#client{module = Mod, rcv_pid = Pid}) -> -spec upgrade_to_tls(client()) -> ok. upgrade_to_tls(#client{module = Mod, rcv_pid = Pid, props = Props}) -> - SSLOpts = proplists:get_value(ssl_opts, Props, [[{verify, verify_none}]]), + HibernateAfter = proplists:get_value(hibernate_after, Props, 500), + DefSslOpts = [[{hibernate_after, HibernateAfter}, {verify, verify_none}]], + SSLOpts = proplists:get_value(ssl_opts, Props, DefSslOpts), Mod:upgrade_to_tls(Pid, SSLOpts). wait_for_close(Client) -> diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl index c1ffd4f..c0bc0aa 100755 --- a/src/escalus_tcp.erl +++ b/src/escalus_tcp.erl @@ -74,8 +74,9 @@ on_connect => fun(), event_client => undefined | escalus_event:event_client(), socket_opts => [gen_tcp:connect_option()], - ssl_opts => [ssl:ssl_option()], - parser_opts => [exml_stream:parser_opt()] + ssl_opts => [ssl:tls_option()], + parser_opts => [exml_stream:parser_opt()], + hibernate_after => timeout() }. %%%=================================================================== @@ -85,7 +86,9 @@ -spec connect([proplists:property()] | opts()) -> pid(). connect(Opts0) -> Opts1 = opts_to_map(Opts0), - {ok, Pid} = gen_server:start_link(?MODULE, [Opts1, self()], []), + Opts2 = overwrite_default_opts(Opts1, default_options()), + GenOpts = maps:to_list(maps:with([hibernate_after], Opts1)), + {ok, Pid} = gen_server:start_link(?MODULE, {Opts2, self()}, GenOpts), Pid. -spec send(pid(), exml_stream:element() | [exml_stream:element()] | binary()) -> ok. @@ -147,7 +150,7 @@ kill(Pid) -> already_stopped end. --spec upgrade_to_tls(pid(), [ssl:ssl_option()]) -> ok. +-spec upgrade_to_tls(pid(), [ssl:tls_option()]) -> ok. upgrade_to_tls(Pid, SSLOpts) -> case gen_server:call(Pid, {upgrade_to_tls, SSLOpts}) of {error, Error} -> @@ -195,18 +198,17 @@ set_active(Pid, Active) -> %%%=================================================================== %%% gen_server callbacks %%%=================================================================== --spec init(list()) -> {ok, state()}. -init([Opts0, Owner]) -> - Opts1 = overwrite_default_opts(Opts0, default_options()), +-spec init({opts(), pid()}) -> {ok, state()}. +init({Opts, Owner}) -> #{ssl := IsSSLConnection, tls_module := TLSMod, on_reply := OnReplyFun, on_request := OnRequestFun, parser_opts := ParserOpts, - event_client := EventClient} = Opts1, - SM = get_stream_management_opt(Opts1), + event_client := EventClient} = Opts, + SM = get_stream_management_opt(Opts), - {ok, Socket} = do_connect(Opts1), + {ok, Socket} = do_connect(Opts), {ok, Parser} = exml_stream:new_parser(ParserOpts), {ok, #state{owner = Owner, socket = Socket, @@ -338,7 +340,8 @@ default_options() -> event_client => undefined, socket_opts => default_socket_options(), ssl_opts => [{verify, verify_none}], - parser_opts => []}. + parser_opts => [], + hibernate_after => 500}. -spec default_socket_options() -> [gen_tcp:connect_option()]. default_socket_options() -> @@ -481,11 +484,12 @@ do_connect(#{ssl := IsSSLConn, on_connect := OnConnectFun, host := Host, port := Port, - ssl_opts := SSLOpts} = Opts) -> + ssl_opts := SSLOpts, + hibernate_after := HibernateAfter} = Opts) -> Address = host_to_inet(Host), SocketOpts = get_socket_opts(Opts), TimeB = erlang:system_time(microsecond), - Reply = maybe_ssl_connection(IsSSLConn, TLSMod, Address, Port, SocketOpts, SSLOpts), + Reply = maybe_ssl_connection(IsSSLConn, TLSMod, Address, Port, SocketOpts, SSLOpts, HibernateAfter), TimeA = erlang:system_time(microsecond), ConnectionTime = TimeA - TimeB, case Reply of @@ -496,12 +500,12 @@ do_connect(#{ssl := IsSSLConn, end, Reply. -maybe_ssl_connection(true, fast_tls, Address, Port, SocketOpts, SSLOpts) -> +maybe_ssl_connection(true, fast_tls, Address, Port, SocketOpts, SSLOpts, _) -> {ok, GenTcpSocket} = gen_tcp:connect(Address, Port, SocketOpts), tcp_to_tls(fast_tls, GenTcpSocket, SSLOpts); -maybe_ssl_connection(true, ssl, Address, Port, SocketOpts, SSLOpts) -> - ssl:connect(Address, Port, SocketOpts ++ SSLOpts); -maybe_ssl_connection(_, _, Address, Port, SocketOpts, _) -> +maybe_ssl_connection(true, ssl, Address, Port, SocketOpts, SSLOpts, HibernateAfter) -> + ssl:connect(Address, Port, SocketOpts ++ SSLOpts ++ [{hibernate_after, HibernateAfter}]); +maybe_ssl_connection(_, _, Address, Port, SocketOpts, _, _) -> gen_tcp:connect(Address, Port, SocketOpts). tcp_to_tls(fast_tls, GenTcpSocket, SSLOpts) -> diff --git a/src/escalus_ws.erl b/src/escalus_ws.erl index dbd32a4..2d2f2b0 100755 --- a/src/escalus_ws.erl +++ b/src/escalus_ws.erl @@ -54,7 +54,7 @@ wslegacy => boolean(), event_client => undefined | escalus_event:event_client(), ssl => boolean(), - ssl_opts => [ssl:ssl_option()], + ssl_opts => [ssl:tls_option()], ws_upgrade_timeout => pos_integer(), stream_management => boolean(), manual_ack => boolean() From 77d0b8b27364cff0d44e54f797eb96c1934f3c55 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 8 Jan 2025 07:52:31 +0100 Subject: [PATCH 13/23] Upgrade exml to 4.0.0 --- rebar.config | 6 +- rebar.lock | 6 +- src/escalus_bosh.erl | 67 +++++------ src/escalus_component.erl | 9 +- src/escalus_event.erl | 10 +- src/escalus_pred.erl | 28 ++--- src/escalus_pubsub_stanza.erl | 102 ++++++++-------- src/escalus_session.erl | 4 +- src/escalus_stanza.erl | 219 ++++++++++++++++------------------ test/escalus_stanza_SUITE.erl | 8 +- 10 files changed, 226 insertions(+), 233 deletions(-) diff --git a/rebar.config b/rebar.config index 3ecc346..9b7dede 100644 --- a/rebar.config +++ b/rebar.config @@ -6,7 +6,7 @@ {require_min_otp_vsn, "26"}. {deps, [ - {exml, "3.4.1", {pkg, hexml}}, + {exml, "4.0.0", {pkg, hexml}}, {meck, "1.0.0"}, {bbmustache, "1.12.2"}, {uuid, "2.0.7", {pkg, uuid_erl}}, @@ -16,6 +16,10 @@ {fast_scram, "0.6.1"} ]}. +{dialyzer, [ + {warnings, [unknown]}, + {plt_extra_apps, [common_test, exml, uuid, worker_pool, fast_scram]}]}. + %% To override the plugin as installed by worker_pool {project_plugins, [rebar3_hex, {rebar3_codecov, "0.6.0"}]}. diff --git a/rebar.lock b/rebar.lock index 7189936..f52ca89 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.2.0", [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, - {<<"exml">>,{pkg,<<"hexml">>,<<"3.4.1">>},0}, + {<<"exml">>,{pkg,<<"hexml">>,<<"4.0.0">>},0}, {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.6">>},1}, {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.6.1">>},0}, {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.21">>},0}, @@ -15,7 +15,7 @@ {pkg_hash,[ {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, - {<<"exml">>, <<"9581FE6512D9772C61BBE611CD4A8E5BB90B4D4481275325EC520F7A931A9393">>}, + {<<"exml">>, <<"54C1AAD5BD290EC31C19CE4A5D449C7E3236107AA2D3610FC04FC963DC8EAF13">>}, {<<"fast_pbkdf2">>, <<"199BCEC73A1A246941E9465D3DC41052953B638128841ED24B29ED03CF70AF27">>}, {<<"fast_scram">>, <<"BEEADB03D774640F0671681759CE53B2FF33CB58C86FD9BF2A793E2FC1ED0F5D">>}, {<<"fast_tls">>, <<"65D7D547A09EEFB37A1C0D04D8601FAC4F3E6E2C1EDE859A7787081670F9648D">>}, @@ -28,7 +28,7 @@ {pkg_hash_ext,[ {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, - {<<"exml">>, <<"D8E7894E2544402B4986EEB2443C15B51B14F686266F091DBF2777D1D99A2FA2">>}, + {<<"exml">>, <<"08CC97527C708D57A03F467049AC260B5951BD67906AA154BE56B5D8BDD3238C">>}, {<<"fast_pbkdf2">>, <<"35EEC22629AAA739915843C7B7DE0D84657D1ECE972D8BBC86368747E9C14012">>}, {<<"fast_scram">>, <<"FE0650A309FDF97C75E1EA812CCFB40EB464ECAFD3783E83AA17C7F572EDAB0B">>}, {<<"fast_tls">>, <<"131542913937025E48CD80AA81F00359686D5501B75621E72026A87B5229505B">>}, diff --git a/src/escalus_bosh.erl b/src/escalus_bosh.erl index 8767c56..f7eb829 100644 --- a/src/escalus_bosh.erl +++ b/src/escalus_bosh.erl @@ -169,49 +169,48 @@ session_creation_body(Rid, To) -> exml:element(). session_creation_body(Wait, Version, Lang, Rid, To, nil) -> empty_body(Rid, nil, - [{<<"content">>, <<"text/xml; charset=utf-8">>}, - {<<"xmlns:xmpp">>, ?NS_BOSH}, - {<<"xmpp:version">>, Version}, - {<<"ver">>, <<"1.6">>}, - {<<"hold">>, <<"1">>}, - {<<"wait">>, list_to_binary(integer_to_list(Wait))}, - {<<"xml:lang">>, Lang}, - {<<"to">>, To}]); + #{<<"content">> => <<"text/xml; charset=utf-8">>, + <<"xmlns:xmpp">> => ?NS_BOSH, + <<"xmpp:version">> => Version, + <<"ver">> => <<"1.6">>, + <<"hold">> => <<"1">>, + <<"wait">> => list_to_binary(integer_to_list(Wait)), + <<"xml:lang">> => Lang, + <<"to">> => To}); session_creation_body(_Wait, _Version, Lang, Rid, To, Sid) -> empty_body(Rid, Sid, - [{<<"xmlns:xmpp">>, ?NS_BOSH}, - {<<"xml:lang">>, Lang}, - {<<"to">>, To}, - {<<"xmpp:restart">>, <<"true">>}]). + #{<<"xmlns:xmpp">> => ?NS_BOSH, + <<"xml:lang">> => Lang, + <<"to">> => To, + <<"xmpp:restart">> => <<"true">>}). -spec session_termination_body(Rid :: integer(), Sid :: binary() | nil) -> exml:element(). session_termination_body(Rid, Sid) -> - Body = empty_body(Rid, Sid, [{<<"type">>, <<"terminate">>}]), + Body = empty_body(Rid, Sid, #{<<"type">> => <<"terminate">>}), Body#xmlel{children = [escalus_stanza:presence(<<"unavailable">>)]}. -spec empty_body(Rid :: integer(), Sid :: binary()) -> exml:element(). empty_body(Rid, Sid) -> - empty_body(Rid, Sid, []). + empty_body(Rid, Sid, #{}). --spec empty_body(Rid :: integer(), Sid :: binary() | nil, ExtraAttrs :: [exml:attr()]) -> +-spec empty_body(Rid :: integer(), Sid :: binary() | nil, ExtraAttrs :: exml:attrs()) -> exml:element(). empty_body(Rid, Sid, ExtraAttrs) -> #xmlel{name = <<"body">>, - attrs = common_attrs(Rid, Sid) ++ ExtraAttrs}. + attrs = maps:merge(common_attrs(Rid, Sid), ExtraAttrs)}. pause_body(Rid, Sid, Seconds) -> Empty = empty_body(Rid, Sid), - Pause = {<<"pause">>, integer_to_binary(Seconds)}, - Empty#xmlel{attrs = Empty#xmlel.attrs ++ [Pause]}. + Empty#xmlel{attrs = maps:put(<<"pause">>, integer_to_binary(Seconds), Empty#xmlel.attrs)}. common_attrs(Rid) -> - [{<<"rid">>, pack_rid(Rid)}, - {<<"xmlns">>, ?NS_HTTP_BIND}]. + #{<<"rid">> => pack_rid(Rid), + <<"xmlns">> => ?NS_HTTP_BIND}. common_attrs(Rid, nil) -> common_attrs(Rid); common_attrs(Rid, Sid) -> - common_attrs(Rid) ++ [{<<"sid">>, Sid}]. + maps:put(<<"sid">>, Sid, common_attrs(Rid)). pack_rid(Rid) -> integer_to_binary(Rid). @@ -576,27 +575,27 @@ handle_recv(#state{replies = [{#xmlel{name = <<"body">>, attrs = Attrs} = Body, wrap_elem(#xmlstreamstart{attrs = Attrs}, #state{rid = Rid, sid = Sid, wait = Wait}) -> - Version = proplists:get_value(<<"version">>, Attrs, <<"1.0">>), - Lang = proplists:get_value(<<"xml:lang">>, Attrs, <<"en">>), - To = proplists:get_value(<<"to">>, Attrs, <<"localhost">>), + Version = maps:get(<<"version">>, Attrs, <<"1.0">>), + Lang = maps:get(<<"xml:lang">>, Attrs, <<"en">>), + To = maps:get(<<"to">>, Attrs, <<"localhost">>), session_creation_body(Wait, Version, Lang, Rid, To, Sid); wrap_elem(#xmlstreamend{}, #state{sid=Sid, rid=Rid}) -> session_termination_body(Rid, Sid); wrap_elem(Element, #state{sid = Sid, rid=Rid}) -> (empty_body(Rid, Sid))#xmlel{children = [Element]}. -unwrap_elem(#xmlel{name = <<"body">>, children = Body, attrs=Attrs}) -> +unwrap_elem(#xmlel{name = <<"body">>, children = Body, attrs = Attrs}) -> Type = detect_type(Attrs), case Type of {streamstart, Ver} -> - Server = proplists:get_value(<<"from">>, Attrs), - StreamStart = #xmlstreamstart{name = <<"stream:stream">>, attrs=[ - {<<"from">>, Server}, - {<<"version">>, Ver}, - {<<"xml:lang">>, <<"en">>}, - {<<"xmlns">>, <<"jabber:client">>}, - {<<"xmlns:stream">>, - <<"http://etherx.jabber.org/streams">>}]}, + Server = maps:get(<<"from">>, Attrs), + NewAttrs = #{<<"from">> => Server, + <<"version">> => Ver, + <<"xml:lang">> => <<"en">>, + <<"xmlns">> => <<"jabber:client">>, + <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>}, + StreamStart = #xmlstreamstart{name = <<"stream:stream">>, + attrs = NewAttrs}, [StreamStart]; streamend -> [escalus_stanza:stream_end()]; @@ -604,7 +603,7 @@ unwrap_elem(#xmlel{name = <<"body">>, children = Body, attrs=Attrs}) -> end ++ Body. detect_type(Attrs) -> - Get = fun(A) -> proplists:get_value(A, Attrs) end, + Get = fun(A) -> maps:get(A, Attrs, undefined) end, case {Get(<<"type">>), Get(<<"xmpp:version">>)} of {<<"terminate">>, _} -> streamend; {_, undefined} -> normal; diff --git a/src/escalus_component.erl b/src/escalus_component.erl index 9ee4478..37b1133 100644 --- a/src/escalus_component.erl +++ b/src/escalus_component.erl @@ -180,7 +180,7 @@ component_start_stream(Conn = #client{props = Props}, []) -> StreamStartRep = escalus_connection:get_stanza(Conn, wait_for_stream), #xmlstreamstart{attrs = Attrs} = StreamStartRep, - Id = proplists:get_value(<<"id">>, Attrs), + Id = maps:get(<<"id">>, Attrs, undefined), {Conn#client{props = [{sid, Id} | Props]}, []}. @@ -205,10 +205,9 @@ component_handshake(Conn = #client{props = Props}, []) -> %% Stanzas %%-------------------------------------------------------------------- component_stream_start(Component) -> - Attrs = [{<<"to">>, Component}, - {<<"xmlns">>, <<"jabber:component:accept">>}, - {<<"xmlns:stream">>, - <<"http://etherx.jabber.org/streams">>}], + Attrs = #{<<"to">> => Component, + <<"xmlns">> => <<"jabber:component:accept">>, + <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>}, #xmlstreamstart{name = <<"stream:stream">>, attrs = Attrs}. component_handshake_el(SID, Password) -> diff --git a/src/escalus_event.erl b/src/escalus_event.erl index ac87c69..4cf2e61 100644 --- a/src/escalus_event.erl +++ b/src/escalus_event.erl @@ -165,13 +165,13 @@ build_elem_event(BaseTime, {story, Type, Time}) -> build_story_event_elem(Type, BaseTime, Time). build_story_event_elem(Type, BaseTime, Time) -> - #xmlel{name = list_to_binary(atom_to_list(Type)), - attrs = [{<<"offset">>, time_offset_binary(BaseTime, Time)}]}. + #xmlel{name = atom_to_binary(Type), + attrs = #{<<"offset">> => time_offset_binary(BaseTime, Time)}}. build_stanza_event_elem(Type, JID, BaseTime, Time, Elem) -> - #xmlel{name = list_to_binary(atom_to_list(Type)), - attrs = [{<<"jid">>, jid_to_binary(JID)}, - {<<"offset">>, time_offset_binary(BaseTime, Time)}], + #xmlel{name = atom_to_binary(Type), + attrs = #{<<"jid">> => jid_to_binary(JID), + <<"offset">> => time_offset_binary(BaseTime, Time)}, children = [Elem || Elem =/= undefined]}. manager(Config) -> diff --git a/src/escalus_pred.erl b/src/escalus_pred.erl index 1105a56..74ac412 100644 --- a/src/escalus_pred.erl +++ b/src/escalus_pred.erl @@ -199,12 +199,12 @@ is_chat_message(Msg, Stanza) -> -spec is_chat_message_from_to(binary(), binary(), binary(), exml:element()) -> boolean(). -is_chat_message_from_to(From, To, Msg, #xmlel{attrs=Attrs} = Stanza) -> +is_chat_message_from_to(From, To, Msg, #xmlel{attrs = Attrs} = Stanza) -> is_chat_message(Msg, Stanza) andalso - bin(From) == proplists:get_value(<<"from">>, Attrs) + bin(From) == maps:get(<<"from">>, Attrs, undefined) andalso - bin(To) == proplists:get_value(<<"to">>, Attrs). + bin(To) == maps:get(<<"to">>, Attrs, undefined). -spec is_groupchat_message(exml:element()) -> boolean(). is_groupchat_message(Stanza) -> @@ -257,24 +257,24 @@ has_type(Type, Stanza) -> -spec is_0184_request(exml:element()) -> boolean(). is_0184_request(#xmlel{children = Els}) -> #xmlel{ name = <<"request">>, - attrs = [{<<"xmlns">>, <<"urn:xmpp:receipts">>}], + attrs = #{<<"xmlns">> => <<"urn:xmpp:receipts">>}, children = [] } =:= lists:keyfind(<<"request">>, 2, Els). -spec is_0184_receipt(exml:element(), exml:element()) -> boolean(). -is_0184_receipt(#xmlel{ attrs = ReqAttrs } = Request, Receipt) -> - {_, ReqTo} = lists:keyfind(<<"to">>, 1, ReqAttrs), +is_0184_receipt(#xmlel{attrs = ReqAttrs} = Request, Receipt) -> + ReqTo = maps:get(<<"to">>, ReqAttrs), is_0184_receipt(Request, ReqTo, Receipt). -spec is_0184_receipt(exml:element(), binary(), exml:element()) -> boolean(). -is_0184_receipt(#xmlel{ attrs = ReqAttrs } = _Request, +is_0184_receipt(#xmlel{attrs = ReqAttrs} = _Request, ProperResFrom, - #xmlel{ attrs = ResAttrs, - children = [#xmlel{ name = <<"received">>, - attrs = SubAttrs}]} = _Receipt) -> - {_, ResFrom} = lists:keyfind(<<"from">>, 1, ResAttrs), - {_, ReqID} = lists:keyfind(<<"id">>, 1, ReqAttrs), - {_, ResID} = lists:keyfind(<<"id">>, 1, SubAttrs), - {_, ResXmlns} = lists:keyfind(<<"xmlns">>, 1, SubAttrs), + #xmlel{attrs = ResAttrs, + children = [#xmlel{name = <<"received">>, + attrs = SubAttrs}]} = _Receipt) -> + ResFrom = maps:get(<<"from">>, ResAttrs), + ReqID = maps:get(<<"id">>, ReqAttrs), + ResID = maps:get(<<"id">>, SubAttrs), + ResXmlns = maps:get(<<"xmlns">>, SubAttrs), binary:longest_common_prefix([ProperResFrom, ResFrom]) == byte_size(ProperResFrom) andalso ReqID == ResID diff --git a/src/escalus_pubsub_stanza.erl b/src/escalus_pubsub_stanza.erl index c92aad9..bd8bbe8 100644 --- a/src/escalus_pubsub_stanza.erl +++ b/src/escalus_pubsub_stanza.erl @@ -59,10 +59,10 @@ -spec discover_nodes(escalus_utils:jid_spec(), binary(), binary() | pubsub_node_id()) -> exml:element(). discover_nodes(User, Id, {NodeAddr, NodeName}) -> - QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, [{<<"node">>, NodeName}], []), + QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, #{<<"node">> => NodeName}, []), iq(<<"get">>, User, Id, NodeAddr, [QueryElement]); discover_nodes(User, Id, NodeAddr) -> - QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, [], []), + QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, #{}, []), iq(<<"get">>, User, Id, NodeAddr, [QueryElement]). %% ---------------- create & delete ---------------- @@ -86,8 +86,8 @@ delete_node(User, Id, {NodeAddr, NodeName}) -> -spec get_configuration(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element(). get_configuration(User, Id, {NodeAddr, NodeName}) -> - Elements = [#xmlel{ name = <<"configure">>, - attrs = [{<<"node">>, NodeName}] }], + Elements = [#xmlel{name = <<"configure">>, + attrs = #{<<"node">> => NodeName}}], pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements). -spec set_configuration(escalus_utils:jid_spec(), binary(), @@ -107,20 +107,20 @@ get_default_configuration(User, Id, NodeAddr) -> -spec get_affiliations(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element(). get_affiliations(User, Id, {NodeAddr, NodeName}) -> - Elements = [#xmlel{ name = <<"affiliations">>, - attrs = [{<<"node">>, NodeName}] }], + Elements = [#xmlel{name = <<"affiliations">>, + attrs = #{<<"node">> => NodeName}}], pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements). -spec set_affiliations(escalus_utils:jid_spec(), binary(), pubsub_node_id(), [{escalus_utils:jid_spec(), binary()}]) -> exml:element(). set_affiliations(User, Id, {NodeAddr, NodeName}, AffChange) -> - AffList = [ #xmlel{ name = <<"affiliation">>, - attrs = [{<<"jid">>, escalus_utils:get_short_jid(U)}, - {<<"affiliation">>, A}] } + AffList = [ #xmlel{name = <<"affiliation">>, + attrs = #{<<"jid">> => escalus_utils:get_short_jid(U), + <<"affiliation">> => A}} || {U, A} <- AffChange ], - Affiliations = #xmlel{ name = <<"affiliations">>, attrs = [{<<"node">>, NodeName}], - children = AffList }, + Affiliations = #xmlel{name = <<"affiliations">>, attrs = #{<<"node">> => NodeName}, + children = AffList}, pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, [Affiliations]). %% ---------------- subscriptions ---------------- @@ -146,7 +146,7 @@ submit_subscription_response(User, Id, {NodeAddr, _NodeName}, Form) -> Fields = [ encode_form_field(F) || F <- Form ], XEl = escalus_stanza:x_data_form(<<"submit">>, Fields), Msg = #xmlel{ name = <<"message">>, - attrs = [{<<"to">>, NodeAddr}, {<<"id">>, Id}], + attrs = #{<<"to">> => NodeAddr, <<"id">> => Id}, children = [XEl] }, escalus_stanza:from(Msg, escalus_utils:get_jid(User)). @@ -320,31 +320,31 @@ optional_form(FormName, NodeName, Type, Fields) -> %% Elements create_node_element(NodeName) -> - #xmlel{name = <<"create">>, attrs = [{<<"node">>, NodeName}]}. + #xmlel{name = <<"create">>, attrs = #{<<"node">> => NodeName}}. pubsub_element(Children, NS) -> #xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, NS}], + attrs = #{<<"xmlns">> => NS}, children = Children}. delete_element(NodeName) -> #xmlel{name = <<"delete">>, - attrs = [{<<"node">>, NodeName}]}. + attrs = #{<<"node">> => NodeName}}. subscribe_element(NodeName, User) -> #xmlel{name = <<"subscribe">>, - attrs = [{<<"node">>, NodeName}, - {<<"jid">>, escalus_utils:get_jid(User)}]}. + attrs = #{<<"node">> => NodeName, + <<"jid">> => escalus_utils:get_jid(User)}}. unsubscribe_element(NodeName, User) -> #xmlel{name = <<"unsubscribe">>, - attrs = [{<<"node">>, NodeName}, - {<<"jid">>, escalus_utils:get_jid(User)}]}. + attrs = #{<<"node">> => NodeName, + <<"jid">> => escalus_utils:get_jid(User)}}. publish_element(NodeName, Item) -> #xmlel{name = <<"publish">>, - attrs = [{<<"node">>, NodeName}], - children = skip_undefined([Item])}. + attrs = #{<<"node">> => NodeName}, + children = maybe_children([Item])}. publish_options_element(Fields) -> #xmlel{name = <<"publish-options">>, @@ -358,63 +358,63 @@ x_data_fields(Fields) -> form_type_field() -> [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}], + attrs = #{<<"var">> => <<"FORM_TYPE">>, <<"type">> => <<"hidden">>}, children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, ?NS_PUBSUB_PUBLISH_OPTIONS}]}]}]. + children = [#xmlcdata{content = ?NS_PUBSUB_PUBLISH_OPTIONS}]}]}]. items_element(NodeName) -> #xmlel{name = <<"items">>, - attrs = [{<<"node">>, NodeName}]}. + attrs = #{<<"node">> => NodeName}}. items_element(NodeName, MaxItems) -> #xmlel{name = <<"items">>, - attrs = [{<<"node">>, NodeName}, - {<<"max_items">>, integer_to_binary(MaxItems)}]}. + attrs = #{<<"node">> => NodeName, + <<"max_items">> => integer_to_binary(MaxItems)}}. item_element(ContentElement) -> #xmlel{name = <<"item">>, - children = skip_undefined([ContentElement])}. + children = maybe_children([ContentElement])}. item_element(ItemId, ContentElement) -> #xmlel{name = <<"item">>, - attrs = [{<<"id">>, ItemId}], - children = skip_undefined([ContentElement])}. + attrs = #{<<"id">> => ItemId}, + children = maybe_children([ContentElement])}. retract_item(NodeName, ItemId) -> #xmlel{name = <<"retract">>, - attrs = [{<<"node">>, NodeName}], - children = [#xmlel{ name = <<"item">>, - attrs = [{<<"id">>, ItemId}] }]}. + attrs = #{<<"node">> => NodeName}, + children = [#xmlel{name = <<"item">>, + attrs = #{<<"id">> => ItemId} }]}. purge_element(NodeName) -> #xmlel{name = <<"purge">>, - attrs = [{<<"node">>, NodeName}]}. + attrs = #{<<"node">> => NodeName}}. subscriptions_element() -> #xmlel{name = <<"subscriptions">>}. subscriptions_element(NodeName, Children) -> #xmlel{name = <<"subscriptions">>, - attrs = [{<<"node">>, NodeName}], + attrs = #{<<"node">> => NodeName}, children = Children}. subscription_element(User, SubscriptionState) -> #xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, escalus_utils:get_jid(User)}, - {<<"subscription">>, SubscriptionState}]}. + attrs = #{<<"jid">> => escalus_utils:get_jid(User), + <<"subscription">> => SubscriptionState}}. subscription_options(NodeName, User) -> #xmlel{name = <<"options">>, - attrs = [{<<"node">>, NodeName}, - {<<"jid">>, escalus_utils:get_jid(User)}]}. + attrs = #{<<"node">> => NodeName, + <<"jid">> => escalus_utils:get_jid(User)}}. set_subscription_options_form(NodeName, User, Children) -> #xmlel{name = <<"options">>, - attrs = [{<<"node">>, NodeName}, - {<<"jid">>, escalus_utils:get_jid(User)}], + attrs = #{<<"node">> => NodeName, + <<"jid">> => escalus_utils:get_jid(User)}, children = [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, <<"jabber:x:data">>}, - {<<"type">>, <<"submit">>}], + attrs = #{<<"xmlns">> => <<"jabber:x:data">>, + <<"type">> => <<"submit">>}, children = Children}]}. default_element() -> @@ -422,18 +422,18 @@ default_element() -> form_element(FormName, NodeName, FieldElements) -> #xmlel{name = FormName, - attrs = skip_undefined([{<<"node">>, NodeName}]), + attrs = skip_undefined(#{<<"node">> => NodeName}), children = [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, <<"jabber:x:data">>}, - {<<"type">>, <<"submit">>}], + attrs = #{<<"xmlns">> => <<"jabber:x:data">>, + <<"type">> => <<"submit">>}, children = FieldElements} ]}. form_type_field_element(FormType) -> Content = << <<"http://jabber.org/protocol/pubsub#">>/binary, FormType/binary >>, #xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], + attrs = #{<<"var">> => <<"FORM_TYPE">>, + <<"type">> => <<"hidden">>}, children = [#xmlel{name = <<"value">>, children = [#xmlcdata{content = Content}]}]}. @@ -447,12 +447,16 @@ encode_form_field({Var, _Type, Value}) -> encode_form_field(Var, Values) -> Children = [ #xmlel{name = <<"value">>, children = [#xmlcdata{content = Content}]} || Content <- Values ], - #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], children = Children}. + #xmlel{name = <<"field">>, attrs = #{<<"var">> => Var}, children = Children}. %% Helpers -skip_undefined(L) -> +maybe_children(L) -> lists:filter(fun(undefined) -> false; ({_, undefined}) -> false; (_) -> true end, L). + + +skip_undefined(L) -> + maps:filter(fun(_, Val) -> undefined =/= Val end, L). diff --git a/src/escalus_session.erl b/src/escalus_session.erl index 90d5247..09a6728 100644 --- a/src/escalus_session.erl +++ b/src/escalus_session.erl @@ -334,11 +334,11 @@ get_sasl_mechanisms(Features) -> exml_query:paths(Features, [{element, <<"mechanisms">>}, {element, <<"mechanism">>}, cdata]). --spec get_server_caps(exml:element()) -> map(). +-spec get_server_caps(exml:element()) -> undefined | map(). get_server_caps(Features) -> case exml_query:subelement(Features, <<"c">>) of #xmlel{attrs = Attrs} -> - maps:from_list(Attrs); + Attrs; _ -> undefined end. diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index 0f9c53f..ce6b358 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -150,60 +150,59 @@ stream_start(Server, XMLNS) -> #xmlstreamstart{name = <<"stream:stream">>, - attrs = [{<<"to">>, Server}, - {<<"version">>, <<"1.0">>}, - {<<"xml:lang">>, <<"en">>}, - {<<"xmlns">>, XMLNS}, - {<<"xmlns:stream">>, - <<"http://etherx.jabber.org/streams">>}]}. + attrs = #{<<"to">> => Server, + <<"version">> => <<"1.0">>, + <<"xml:lang">> => <<"en">>, + <<"xmlns">> => XMLNS, + <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>}}. stream_end() -> #xmlstreamend{name = <<"stream:stream">>}. ws_open(Server) -> - #xmlel{name= <<"open">>, attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}, - {<<"to">>, Server}, - {<<"version">>,<<"1.0">>}]}. + #xmlel{name= <<"open">>, + attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>, + <<"to">> => Server, + <<"version">> => <<"1.0">>}}. ws_close()-> - #xmlel{name= <<"close">>, attrs = [ {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} ]}. + #xmlel{name= <<"close">>, + attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>}}. starttls() -> #xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-tls">>}]}. + attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-tls">>}}. compress(Method) -> #xmlel{name = <<"compress">>, - attrs = [{<<"xmlns">>, <<"http://jabber.org/protocol/compress">>}], + attrs = #{<<"xmlns">> => <<"http://jabber.org/protocol/compress">>}, children = [#xmlel{name = <<"method">>, children = [#xmlcdata{content = Method}]}]}. -spec iq(binary(), [exml:element()]) -> exml:element(). iq(Type, Body) -> #xmlel{name = <<"iq">>, - attrs = [{<<"type">>, Type}, - {<<"id">>, id()}], + attrs = #{<<"type">> => Type, <<"id">> => id()}, children = Body}. iq(To, Type, Body) -> IQ = iq(Type, Body), - IQ#xmlel{attrs = [{<<"to">>, To} | IQ#xmlel.attrs]}. + IQ#xmlel{attrs = maps:put(<<"to">>, To, IQ#xmlel.attrs)}. %% slightly naughty, this isn't a stanza but it will go inside an query_el(NS, Children) -> - query_el(NS, [], Children). + query_el(NS, #{}, Children). query_el(NS, Attrs, Children) -> #xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, NS} | Attrs], + attrs = maps:put(<<"xmlns">>, NS, Attrs), children = Children}. %% http://xmpp.org/extensions/xep-0004.html %% slightly naughty - this isn't a stanza but can be a child of various stanza types x_data_form(Type, Children) -> #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>,?NS_DATA_FORMS}, - {<<"type">>, Type}], + attrs = #{<<"xmlns">> => ?NS_DATA_FORMS, <<"type">> => Type}, children = Children}. -spec bind(binary()) -> exml:element(). @@ -215,14 +214,14 @@ bind(Resource) -> end, iq(<<"set">>, [#xmlel{name = <<"bind">>, - attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-bind">>}], + attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-bind">>}, children = Children}]). -spec session() -> exml:element(). session() -> NS = <<"urn:ietf:params:xml:ns:xmpp-session">>, iq(<<"set">>, [#xmlel{name = <<"session">>, - attrs = [{<<"xmlns">>, NS}]}]). + attrs = #{<<"xmlns">> => NS}}]). to(Stanza, Recipient) when is_binary(Recipient) -> setattr(Stanza, <<"to">>, Recipient); @@ -238,7 +237,7 @@ set_id(Stanza, ID) -> setattr(Stanza, <<"id">>, ID). setattr(Stanza, Key, Val) -> - NewAttrs = lists:keystore(Key, 1, Stanza#xmlel.attrs, {Key, Val}), + NewAttrs = maps:put(Key, Val, Stanza#xmlel.attrs), Stanza#xmlel{attrs = NewAttrs}. tags(KVs) -> @@ -253,7 +252,7 @@ presence(<<"available">>, Children) -> #xmlel{name = <<"presence">>, children = Children}; presence(Type, Children) -> #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, bin(Type)}], + attrs = #{<<"type">> => bin(Type)}, children = Children}. presence_direct(Recipient, Type) -> @@ -284,9 +283,9 @@ presence_show(Show) -> error_element(Type, Condition) -> #xmlel{name = <<"error">>, - attrs = [{<<"type">>, Type}], + attrs = #{<<"type">> => Type}, children = [#xmlel{name = Condition, - attrs = [{<<"xmlns">>, ?NS_STANZA_ERRORS}]}]}. + attrs = #{<<"xmlns">> => ?NS_STANZA_ERRORS}}]}. -spec message(From, Recipient, Type, Msg) -> exml:element() when From :: undefined | binary(), @@ -356,26 +355,22 @@ chat_without_carbon_to(Recipient, Msg) -> Stanza = #xmlel{children = Children} = chat_to(Recipient, Msg), Stanza#xmlel{children = Children ++ [#xmlel{name = <<"private">>, - attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}]}. + attrs = #{<<"xmlns">> => ?NS_CARBONS_2}}]}. -receipt_req(#xmlel{ name = <<"message">>, - attrs = Attrs, - children = Children } = Msg) -> +receipt_req(#xmlel{name = <<"message">>, attrs = Attrs, children = Children } = Msg) -> ReqStanza = receipt_req_elem(), - Msg2 = case lists:keysearch(<<"id">>, 1, Attrs) of - {value, _} -> + Msg2 = case maps:find(<<"id">>, Attrs) of + {ok, _} -> Msg; - _ -> - Msg#xmlel{ attrs = [{<<"id">>, id()} | Attrs] } + error -> + Msg#xmlel{attrs = maps:put(<<"id">>, id(), Attrs)} end, Msg2#xmlel{ children = [ReqStanza | Children] }. -receipt_conf(#xmlel{ attrs = Attrs, children = Children }) -> - {value, {_, ID}} = lists:keysearch(<<"id">>, 1, Attrs), - {value, {_, From}} = lists:keysearch(<<"from">>, 1, Attrs), - Type = case lists:keyfind(<<"type">>, 1, Attrs) of - false -> <<"chat">>; - {_, Type0} -> Type0 +receipt_conf(#xmlel{attrs = #{<<"id">> := ID, <<"from">> := From} = Attrs, children = Children}) -> + Type = case maps:find(<<"type">>, Attrs) of + error -> <<"chat">>; + {ok, Type0} -> Type0 end, To = case lists:keyfind(<<"received">>, #xmlel.name, Children) of #xmlel{ name = <<"received">> } -> @@ -392,21 +387,21 @@ receipt_conf(#xmlel{ attrs = Attrs, children = Children }) -> end end, #xmlel{ name = <<"message">>, - attrs = [{<<"to">>, To}, {<<"id">>, id()}, {<<"type">>, Type}], + attrs = #{<<"to">> => To, <<"id">> => id(), <<"type">> => Type}, children = [receipt_conf_elem(ID)] }. receipt_req_elem() -> #xmlel{ name = <<"request">>, - attrs = [{<<"xmlns">>, ?NS_RECEIPTS}], + attrs = #{<<"xmlns">> => ?NS_RECEIPTS}, children = [] }. receipt_conf_elem(ID) -> #xmlel{ name = <<"received">>, - attrs = [{<<"xmlns">>, ?NS_RECEIPTS}, {<<"id">>, ID}], + attrs = #{<<"xmlns">> => ?NS_RECEIPTS, <<"id">> => ID}, children = [] }. @@ -415,16 +410,16 @@ groupchat_to(Recipient, Msg) -> get_registration_fields() -> iq(<<"get">>, [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, <<"jabber:iq:register">>}]}]). + attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}}]). register_account(Body) -> iq(<<"set">>, [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, <<"jabber:iq:register">>}], + attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = Body}]). remove_account() -> iq(<<"set">>, [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, <<"jabber:iq:register">>}], + attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = [#xmlel{name = <<"remove">>}]}]). iq_result(Request) -> @@ -433,14 +428,13 @@ iq_result(Request) -> iq_result(Request, Payload) -> ToAttr = case exml_query:attr(Request, <<"from">>) of undefined -> - []; + #{}; Jid -> - [{<<"to">>, Jid}] + #{<<"to">> => Jid} end, Id = exml_query:attr(Request, <<"id">>), - Attrs = ToAttr ++ [{<<"id">>, Id}, {<<"type">>, <<"result">>}], #xmlel{name = <<"iq">>, - attrs = Attrs, + attrs = ToAttr#{<<"id">> => Id, <<"type">> => <<"result">>}, children = Payload}. iq_get(NS, Payload) -> @@ -455,14 +449,14 @@ iq_set_nonquery(NS, Payload) -> iq_with_type(Type, NS, Payload) -> iq(Type, [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, NS}], + attrs = #{<<"xmlns">> => NS}, children = Payload}]). iq_with_type(Type, NS, Payload, nonquery) -> #xmlel{name = <<"iq">>, - attrs = [{<<"xmlns">>, NS}, - {<<"type">>, Type}, - {<<"id">>, id()}], + attrs = #{<<"xmlns">> => NS, + <<"type">> => Type, + <<"id">> => id()}, children = Payload}. roster_get() -> @@ -470,7 +464,7 @@ roster_get() -> roster_get(Ver) -> #xmlel{children = [Query]} = Stanza = iq_get(?NS_ROSTER, []), - NewQuery = Query#xmlel{attrs = [{<<"ver">>, Ver} | Query#xmlel.attrs]}, + NewQuery = Query#xmlel{attrs = maps:put(<<"ver">>, Ver, Query#xmlel.attrs)}, Stanza#xmlel{children = [NewQuery]}. roster_add_contacts(ItemSpecs) -> @@ -481,9 +475,8 @@ roster_add_contacts(ItemSpecs) -> %% whether to use bare or full jid. contact_item({User, Groups, Nick}) -> #xmlel{name = <<"item">>, - attrs = [%% XXX - {<<"jid">>, escalus_utils:get_short_jid(User)}, - {<<"name">>, bin(Nick)}], + attrs = #{<<"jid">> => escalus_utils:get_short_jid(User), + <<"name">> => bin(Nick)}, children = [#xmlel{name = <<"group">>, children = [#xmlcdata{content = bin(Group)}]} || Group <- Groups]}. @@ -495,16 +488,15 @@ roster_add_contact(User, Groups, Nick) -> roster_remove_contact(User) -> iq_set(?NS_ROSTER, [#xmlel{name = <<"item">>, - attrs = [%% XXX - {<<"jid">>, escalus_utils:get_short_jid(User)}, - {<<"subscription">>, <<"remove">>}]}]). + attrs = #{<<"jid">> => escalus_utils:get_short_jid(User), + <<"subscription">> => <<"remove">>}}]). private_set(Element) -> iq_set(?NS_PRIVATE, [Element]). private_get(NS, Name) -> Element = #xmlel{name = bin(Name), - attrs = [{<<"xmlns">>, bin(NS)}]}, + attrs = #{<<"xmlns">> => bin(NS)}}, iq_get(?NS_PRIVATE, [Element]). last_activity(User) -> @@ -515,23 +507,23 @@ privacy_get_all() -> privacy_get_lists(ListNames) -> iq_get(?NS_PRIVACY, [#xmlel{name = <<"list">>, - attrs = [{<<"name">>, bin(Name)}]} + attrs = #{<<"name">> => bin(Name)}} || Name <- ListNames]). privacy_set_list(PrivacyList) -> iq_set(?NS_PRIVACY, [PrivacyList]). privacy_activate(ListName) -> - privacy_set(<<"active">>, [{<<"name">>, bin(ListName)}]). + privacy_set(<<"active">>, #{<<"name">> => bin(ListName)}). privacy_deactivate()-> - privacy_set(<<"active">>, []). + privacy_set(<<"active">>, #{}). privacy_set_default(ListName) -> - privacy_set(<<"default">>, [{<<"name">>, bin(ListName)}]). + privacy_set(<<"default">>, #{<<"name">> => bin(ListName)}). privacy_no_default()-> - privacy_set(<<"default">>, []). + privacy_set(<<"default">>, #{}). privacy_set(What, Attrs) -> iq_set(?NS_PRIVACY, [#xmlel{name = What, attrs = Attrs}]). @@ -539,21 +531,21 @@ privacy_set(What, Attrs) -> %% Create empty list element with given name. privacy_list(Name, Items) -> #xmlel{name = <<"list">>, - attrs = [{<<"name">>, Name}], + attrs = #{<<"name">> => Name}, children = Items}. privacy_list_item(Order, Action, Content) -> #xmlel{name = <<"item">>, - attrs = [{<<"order">>, Order}, - {<<"action">>, Action}], + attrs = #{<<"order">> => Order, + <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. privacy_list_item(Order, Action, Type, Value, Content) -> #xmlel{name = <<"item">>, - attrs = [{<<"order">>, Order}, - {<<"type">>, Type}, - {<<"value">>, Value}, - {<<"action">>, Action}], + attrs = #{<<"order">> => Order, + <<"type">> => Type, + <<"value">> => Value, + <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. privacy_list_jid_item(Order, Action, Who, Contents) -> @@ -565,14 +557,14 @@ disco_info(JID) -> iq(JID, <<"get">>, [Query]). disco_info(JID, Node) -> - Query = query_el(?NS_DISCO_INFO, [{<<"node">>, Node}], []), + Query = query_el(?NS_DISCO_INFO, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [Query]). disco_items(JID) -> ItemsQuery = query_el(?NS_DISCO_ITEMS, []), iq(JID, <<"get">>, [ItemsQuery]). disco_items(JID, Node) -> - ItemsQuery = query_el(?NS_DISCO_ITEMS, [{<<"node">>, Node}], []), + ItemsQuery = query_el(?NS_DISCO_ITEMS, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [ItemsQuery]). search_fields([]) -> @@ -581,9 +573,9 @@ search_fields([null|Rest]) -> [#xmlel{name = <<"field">>} | search_fields(Rest)]; search_fields([{Key, Val}|Rest]) -> [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, Key}], + attrs = #{<<"var">> => Key}, children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, Val}]}]} + children = [#xmlcdata{content = Val}]}]} | search_fields(Rest)]. search_fields_iq(JID) -> @@ -611,18 +603,14 @@ vcard([{_,_}|_] = Tuples) -> vcard(tuples_to_fields(Tuples)); vcard(Body) -> #xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>,<<"vcard-temp">>}], + attrs = #{<<"xmlns">> => <<"vcard-temp">>}, children = Body}. cdata_field(Name, Value) -> - #xmlel{name = Name, - attrs = [], - children = [{xmlcdata, Value}]}. + #xmlel{name = Name, children = [#xmlcdata{content = Value}]}. field(Name, Children) -> - #xmlel{name = Name, - attrs = [], - children = Children}. + #xmlel{name = Name, children = Children}. tuples_to_fields([]) -> []; @@ -637,14 +625,14 @@ adhoc_request(Node) -> adhoc_request(Node, Payload) -> iq(<<"set">>, [#xmlel{name = <<"command">>, - attrs = [{<<"xmlns">>, ?NS_ADHOC}, - {<<"node">>, Node}, - {<<"action">>, <<"execute">>}], + attrs = #{<<"xmlns">> => ?NS_ADHOC, + <<"node">> => Node, + <<"action">> => <<"execute">>}, children = Payload}]). ping_request(To) -> IQ = iq(<<"get">>, [#xmlel{name = <<"ping">>, - attrs = [{<<"xmlns">>, ?NS_PING}] + attrs = #{<<"xmlns">> => ?NS_PING} }]), to(IQ, To). @@ -661,8 +649,8 @@ auth(Mechanism) -> -spec auth(binary(), [#xmlcdata{}]) -> #xmlel{}. auth(Mechanism, Children) -> #xmlel{name = <<"auth">>, - attrs = [{<<"xmlns">>, ?NS_SASL}, - {<<"mechanism">>, Mechanism}], + attrs = #{<<"xmlns">> => ?NS_SASL, + <<"mechanism">> => Mechanism}, children = Children}. auth_response() -> @@ -670,33 +658,36 @@ auth_response() -> auth_response(Children) -> #xmlel{name = <<"response">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], + attrs = #{<<"xmlns">> => ?NS_SASL}, children = Children}. enable_sm() -> #xmlel{name = <<"enable">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. + attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. enable_sm(Opts) -> - #xmlel{name = <<"enable">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}] - ++ [{<<"resume">>, <<"true">>} - || true == proplists:is_defined(resume, Opts)]}. + case proplists:is_defined(resume, Opts) of + true -> + #xmlel{name = <<"enable">>, + attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, <<"resume">> => <<"true">>}}; + false -> + enable_sm() + end. sm_request() -> #xmlel{name = <<"r">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. + attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. sm_ack(H) -> #xmlel{name = <<"a">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}, - {<<"h">>, integer_to_binary(H)}]}. + attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, + <<"h">> => integer_to_binary(H)}}. resume(SMID, PrevH) -> #xmlel{name = <<"resume">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}, - {<<"previd">>, SMID}, - {<<"h">>, integer_to_binary(PrevH)}]}. + attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, + <<"previd">> => SMID, + <<"h">> => integer_to_binary(PrevH)}}. %% XEP-0313 Mam @@ -713,9 +704,8 @@ field_el(Name, Type, Values) when is_list(Values) -> children = [#xmlcdata{content = E}]} end, Values), #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, - {<<"var">>, Name}], - children = Fields }; + attrs = #{<<"type">> => Type, <<"var">> => Name}, + children = Fields}; field_el(Name, Type, Value) -> field_el(Name, Type, [Value]). @@ -730,7 +720,7 @@ mam_archive_query(QueryId, Children) -> ChildEl = case length(DefChilds) > 1 of true -> [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, <<"jabberd:x:data">>}], + attrs = #{<<"xmlns">> => <<"jabberd:x:data">>}, children = DefChilds}]; false -> %% no need to create form element @@ -741,7 +731,7 @@ mam_archive_query(QueryId, Children) -> <<"set">>, [#xmlel{ name = <<"query">>, - attrs = [mam_ns_attr(), {<<"queryid">>, QueryId}], + attrs = #{<<"xmlns">> => ?NS_MAM, <<"queryid">> => QueryId}, children = ChildEl}]). @@ -786,7 +776,7 @@ defined(L) when is_list(L) -> [ El || El <- L, El /= undefined ]. rsm_after_or_before({Direction, AbstractID, MaxCount}) -> #xmlel{name = <<"set">>, - attrs = [{<<"xmlns">>, ?NS_RSM}], + attrs = #{<<"xmlns">> => ?NS_RSM}, children = defined([max(MaxCount), direction_el(Direction, AbstractID) ])}. direction_el('after', AbstractID) when is_binary(AbstractID) -> @@ -804,8 +794,6 @@ max(N) when is_integer(N) -> max(_) -> undefined. -mam_ns_attr() -> {<<"xmlns">>,?NS_MAM}. - %% XEP-0280 Carbons %% @@ -817,11 +805,11 @@ carbons_disable() -> disable_carbons_el() -> #xmlel{name = <<"disable">>, - attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}. + attrs = #{<<"xmlns">> => ?NS_CARBONS_2}}. enable_carbons_el() -> #xmlel{name = <<"enable">>, - attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}. + attrs = #{<<"xmlns">> => ?NS_CARBONS_2}}. %% XEP-0333 Chat Markers %% @@ -832,18 +820,17 @@ markable(Message = #xmlel{name = <<"message">>, children = Children}) -> -spec chat_marker(escalus_utils:jid_spec(), binary(), binary()) -> exml:element(). chat_marker(To, MarkerName, MessageId) -> #xmlel{name = <<"message">>, - attrs = [{<<"to">>, escalus_utils:get_jid(To)}], + attrs = #{<<"to">> => escalus_utils:get_jid(To)}, children = [marker_el(MarkerName, MessageId)]}. markable_el() -> - #xmlel{name = <<"markable">>, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}]}. + #xmlel{name = <<"markable">>, attrs = #{<<"xmlns">> => ?NS_CHAT_MARKERS}}. marker_el(MarkerName, MessageId) when MarkerName =:= <<"received">> orelse MarkerName =:= <<"displayed">> orelse MarkerName =:= <<"acknowledged">>, is_binary(MessageId) -> - #xmlel{name = MarkerName, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}, - {<<"id">>, MessageId}]}. + #xmlel{name = MarkerName, attrs = #{<<"xmlns">> => ?NS_CHAT_MARKERS, <<"id">> => MessageId}}. -spec id() -> binary(). id() -> diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index 90fbae6..fb848d7 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -28,14 +28,14 @@ nullary_snippet_to_xmlel(_) -> unary_snippet_to_xmlel(_) -> M = escalus_stanza, ?eq(#xmlel{name = <<"el">>, - attrs = [{<<"attr">>, <<"value">>}]}, + attrs = #{<<"attr">> => <<"value">>}}, M:from_template("", [{val, "value"}])). type_matrix_accepted(_) -> M = escalus_stanza, Example = #xmlel{name = <<"el">>, - attrs = [{<<"attr">>, <<"value">>}]}, + attrs = #{<<"attr">> => <<"value">>}}, ?eq(Example, M:from_template("", [{val, "value"}])), ?eq(Example, M:from_template(<<"">>, [{val, "value"}])), ?eq(Example, M:from_template(<<"">>, [{val, <<"value">>}])), @@ -52,13 +52,13 @@ attribute_as_argument(_) -> M = escalus_stanza, Attr = {<<"name">>, <<"value">>}, Example = #xmlel{name = <<"el">>, - attrs = [Attr]}, + attrs = #{<<"name">> => <<"value">>}}, ?eq(Example, M:from_template("", [{attr, Attr}])). numbers_as_arguments(_) -> M = escalus_stanza, Example = #xmlel{name = <<"el">>, - attrs = [{<<"int">>, <<"666">>}, {<<"pi">>, <<"3.14">>}]}, + attrs = #{<<"int">> => <<"666">>, <<"pi">> => <<"3.14">>}}, ?eq(Example, M:from_template("", [{int, 666}, {pi, math:pi()}])). From adde5f4f3993838c69a8191cc09f4453964201a3 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 20 Jan 2025 11:48:32 +0100 Subject: [PATCH 14/23] Add missing specs to different modules --- src/escalus.erl | 14 +++++ src/escalus_assert.erl | 1 + src/escalus_bosh.erl | 4 +- src/escalus_bosh_gun.erl | 14 +++-- src/escalus_compat.erl | 5 ++ src/escalus_component.erl | 1 + src/escalus_connection.erl | 3 + src/escalus_ct.erl | 1 + src/escalus_ejabberd.erl | 35 ++++++++++-- src/escalus_event.erl | 15 +++-- src/escalus_fresh.erl | 4 ++ src/escalus_history_h.erl | 14 +++-- src/escalus_mongooseim.erl | 1 + src/escalus_new_assert.erl | 4 ++ src/escalus_pred.erl | 8 ++- src/escalus_pubsub_stanza.erl | 16 +++++- src/escalus_rpc.erl | 3 +- src/escalus_server.erl | 2 + src/escalus_stanza.erl | 103 ++++++++++++++++++++++++++++++---- src/escalus_story.erl | 6 ++ src/escalus_sup.erl | 5 +- src/escalus_utils.erl | 19 ++++++- 22 files changed, 233 insertions(+), 45 deletions(-) diff --git a/src/escalus.erl b/src/escalus.erl index 04efab4..553868a 100644 --- a/src/escalus.erl +++ b/src/escalus.erl @@ -136,46 +136,60 @@ story(Config, ResourceCounts, Story) -> %% Assertions +-spec assert(atom(), term()) -> ok | no_return(). assert(PredSpec, Arg) -> escalus_new_assert:assert(PredSpec, Arg). +-spec assert(atom(), [term()], term()) -> ok | no_return(). assert(PredSpec, Params, Arg) -> escalus_new_assert:assert(PredSpec, Params, Arg). +-spec assert_many([atom()], [exml:element()]) -> ok | no_return(). assert_many(Predicates, Stanzas) -> escalus_new_assert:assert_many(Predicates, Stanzas). %% Client API +-spec send(client(), exml:element()) -> ok. send(Client, Packet) -> escalus_client:send(Client, Packet). +-spec send_and_wait(client(), exml:element()) -> exml:element(). send_and_wait(Client, Packet) -> escalus_client:send_and_wait(Client, Packet). +-spec wait_for_stanza(client()) -> exml:element(). wait_for_stanza(Client) -> escalus_client:wait_for_stanza(Client). +-spec wait_for_stanza(client(), timeout()) -> exml:element(). wait_for_stanza(Client, Timeout) -> escalus_client:wait_for_stanza(Client, Timeout). +-spec wait_for_stanzas(client(), non_neg_integer()) -> [exml:element()]. wait_for_stanzas(Client, Count) -> escalus_client:wait_for_stanzas(Client, Count). +-spec wait_for_stanzas(client(), non_neg_integer(), timeout()) -> [exml:element()]. wait_for_stanzas(Client, Count, Timeout) -> escalus_client:wait_for_stanzas(Client, Count, Timeout). +-spec peek_stanzas(client()) -> [exml:element()]. peek_stanzas(Client) -> escalus_client:peek_stanzas(Client). +-spec send_iq_and_wait_for_result(client(), exml:element()) -> exml:element() | no_return(). send_iq_and_wait_for_result(Client, Iq) -> escalus_client:send_iq_and_wait_for_result(Client, Iq). +-spec send_iq_and_wait_for_result(client(), exml:element(), timeout()) -> + exml:element() | no_return(). send_iq_and_wait_for_result(Client, Iq, Timeout) -> escalus_client:send_iq_and_wait_for_result(Client, Iq, Timeout). %% Other functions +-spec override(config(), atom(), {atom(), atom()}) -> config(). override(Config, OverrideName, NewValue) -> escalus_overridables:override(Config, OverrideName, NewValue). diff --git a/src/escalus_assert.erl b/src/escalus_assert.erl index cdc2e9c..ed0cba0 100644 --- a/src/escalus_assert.erl +++ b/src/escalus_assert.erl @@ -15,6 +15,7 @@ %%============================================================================== -module(escalus_assert). +-compile(nowarn_missing_spec). -export([is_chat_message/2, has_no_stanzas/1, diff --git a/src/escalus_bosh.erl b/src/escalus_bosh.erl index f7eb829..a49a35e 100644 --- a/src/escalus_bosh.erl +++ b/src/escalus_bosh.erl @@ -412,8 +412,8 @@ handle_info({http_reply, Ref, Body, _Transport} = HttpReply, {_, true} -> S1 = handle_http_reply(Ref, XmlBody, S0, Timestamp), S1#state{ pending_replies = [] }; - {{value, {Ref, _Rid, _Pid}}, _} -> - {{value, {Ref, _Rid, _Pid}}, NewRequests} = queue:out(S0#state.requests), + {{value, {Ref, Rid, Pid}}, _} -> + {{value, {Ref, Rid, Pid}}, NewRequests} = queue:out(S0#state.requests), S1 = handle_http_reply(Ref, XmlBody, S0#state{ requests = NewRequests }, Timestamp), lists:foreach(fun(PendingReply) -> self() ! PendingReply end, S1#state.pending_replies), diff --git a/src/escalus_bosh_gun.erl b/src/escalus_bosh_gun.erl index f4dccda..65caa55 100644 --- a/src/escalus_bosh_gun.erl +++ b/src/escalus_bosh_gun.erl @@ -11,8 +11,7 @@ handle_call/3, handle_cast/2, handle_info/2, - terminate/2, - code_change/3]). + terminate/2]). -record(state, {destination, options, @@ -28,9 +27,11 @@ start_link(Args) -> gen_server:start_link(?MODULE, [Args], []). +-spec stop(pid()) -> ok. stop(Pool) -> gen_server:cast(Pool, stop). +-spec request(pid(), iodata(), gun:req_headers(), iodata()) -> term(). request(Pool, Path, Hdrs, Body) -> case get_client(Pool) of {error, _} = Error -> @@ -62,6 +63,8 @@ init([Args]) -> queue = queue:new() }, 0}. +-spec handle_call(get_client, gen_server:from(), state()) -> + {reply, term(), state()} | {noreply, state()}. handle_call(get_client, _From, State = #state{free = [Client | Free], busy = Busy}) -> {reply, Client, State#state{free = Free, @@ -83,6 +86,8 @@ handle_call(get_client, From, State = #state{free = [], when M == T -> {noreply, State#state{queue = queue:in(From, Queue)}}. +-spec handle_cast({free_client, pid()} | stop, state()) -> + {noreply, state()} | {stop, term(), state()}. handle_cast({free_client, Pid}, State = #state{free = Free, busy = Busy, queue = Queue}) -> @@ -98,6 +103,7 @@ handle_cast({free_client, Pid}, State = #state{free = Free, handle_cast(stop, State) -> {stop, normal, State}. +-spec handle_info(term(), state()) -> {noreply, state()}. handle_info({'EXIT', From, _Reason}, State = #state{free = Free, busy = Busy, total = Total}) -> @@ -113,14 +119,12 @@ handle_info(_Info, State) -> ct:pal("Unknown Info in bosh_gun: ~p", [_Info]), {noreply, State}. +-spec terminate(term(), state()) -> ok. terminate(_Reason, #state{free = Free, busy = Busy}) -> [gun:close(F) || F <- Free], [gun:close(B) || B <- Busy], ok. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - connect({Host, Port}, Options) -> {ok, Pid} = gun:open(Host, Port, Options#{protocols => [http]}), {ok, http} = gun:await_up(Pid), diff --git a/src/escalus_compat.erl b/src/escalus_compat.erl index f6e0cc6..8d00081 100644 --- a/src/escalus_compat.erl +++ b/src/escalus_compat.erl @@ -31,6 +31,7 @@ %% Public API %%-------------------------------------------------------------------- +-spec bin(binary() | string() | atom() | integer()) -> binary() | no_return(). bin(Arg) when is_binary(Arg) -> Arg; bin(Arg) when is_list(Arg) -> @@ -46,17 +47,21 @@ bin(Other) -> type_complain("???", Other), error(badarg, [Other]). +-spec deprecated(atom(), atom(), T) -> T. deprecated(Old, New, Result) -> error_logger:info_msg("calling deprecated function ~p, use ~p instead~n~p~n", [Old, New, backtrace(1)]), Result. +-spec unimplemented() -> no_return(). unimplemented() -> throw({unimplemented, backtrace(1)}). +-spec complain(term()) -> ok. complain(What) -> error_logger:info_msg("~s at ~p~n", [What, backtrace(1)]). +-spec backtrace(non_neg_integer()) -> list(). backtrace(N) -> {current_stacktrace, Stacktrace} = erlang:process_info(self(), current_stacktrace), lists:nthtail(N + 1, Stacktrace). diff --git a/src/escalus_component.erl b/src/escalus_component.erl index 37b1133..c1dd981 100644 --- a/src/escalus_component.erl +++ b/src/escalus_component.erl @@ -153,6 +153,7 @@ handle_info(Info, #component_state{module = M, client = C, user_state = S} = Sta {noreply, NewState, ?WAIT_AFTER_STANZA}. +-spec terminate(atom(), state()) -> any(). terminate(Reason, #component_state{client = C, module = M, user_state = S}) -> catch escalus_connection:stop(C), case erlang:function_exported(M, terminate, 2) of diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index 1a69cbf..474922d 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -430,6 +430,7 @@ upgrade_to_tls(#client{module = Mod, rcv_pid = Pid, props = Props}) -> SSLOpts = proplists:get_value(ssl_opts, Props, DefSslOpts), Mod:upgrade_to_tls(Pid, SSLOpts). +-spec wait_for_close(client()) -> boolean(). wait_for_close(Client) -> wait_for_close(Client, default_timeout()). @@ -476,6 +477,8 @@ maybe_forward_to_owner(_, State, Stanzas, Fun, Timestamp) -> stanza_msg(Stanza, Metadata) -> {stanza, self(), Stanza, Metadata}. +-spec separate_ack_requests({boolean(), non_neg_integer(), term()}, [exml_stream:element()]) -> + {{boolean(), non_neg_integer(), term()}, [exml_stream:element()], [exml_stream:element()]}. separate_ack_requests({false, H0, A}, Stanzas) -> %% Don't keep track of H {{false, H0, A}, [], Stanzas}; diff --git a/src/escalus_ct.erl b/src/escalus_ct.erl index a32e1cb..0d51332 100644 --- a/src/escalus_ct.erl +++ b/src/escalus_ct.erl @@ -163,5 +163,6 @@ ct_log_timestamp({MS, S, US}) -> "~2.10.0B:~2.10.0B:~2.10.0B.~3.10.0B", [Year, Month, Day, Hour, Min, Sec, MilliSec])). +-spec log_error(term(), [any()]) -> ok. log_error(Format, Args) -> ct:pal(error, Format, Args). diff --git a/src/escalus_ejabberd.erl b/src/escalus_ejabberd.erl index 96be561..6c80ca9 100644 --- a/src/escalus_ejabberd.erl +++ b/src/escalus_ejabberd.erl @@ -53,21 +53,26 @@ %%% Business API %%% +-spec rpc(atom(), atom(), [any()], timeout()) -> any(). rpc(M, F, A, Timeout) -> Node = escalus_ct:get_config(ejabberd_node), Cookie = escalus_ct:get_config(ejabberd_cookie), escalus_rpc:call(Node, M, F, A, Timeout, Cookie). +-spec rpc(atom(), atom(), [any()]) -> any(). rpc(M, F, A) -> rpc(M, F, A, 3000). +-spec remote_display(string()) -> true. remote_display(String) -> Line = [$\n, [$- || _ <- String], $\n], remote_format("~s~s~s", [Line, String, Line]). +-spec remote_format(string()) -> true. remote_format(Format) -> remote_format(Format, []). +-spec remote_format(string(), [any()]) -> true. remote_format(Format, Args) -> group_leader(rpc(erlang, whereis, [user]), self()), io:format(Format, Args), @@ -106,14 +111,17 @@ with_local_option(Option, Value, Fun) -> end, lists:zip(Hosts, OldValues)) end. -get_c2s_status(#client{jid=Jid}) -> +-spec get_c2s_status(escalus:client()) -> term(). +get_c2s_status(#client{jid = Jid}) -> {match, USR} = re:run(Jid, <<"([^@]*)@([^/]*)/(.*)">>, [{capture, all_but_first, list}]), Pid = rpc(ejabberd_sm, get_session_pid, USR), rpc(sys, get_status, [Pid]). +-spec wait_for_session_count(escalus:config(), non_neg_integer()) -> ok | no_return(). wait_for_session_count(Config, Count) -> wait_for_session_count(Config, Count, 0). +-spec get_remote_sessions(escalus:config()) -> term(). get_remote_sessions(Config) -> escalus_overridables:do(Config, get_remote_sessions, [], {?MODULE, default_get_remote_sessions}). @@ -203,8 +211,13 @@ reset_option({Option, _, Set, _}, Config) -> %% escalus_user_db callbacks %%-------------------------------------------------------------------- -start(_) -> ok. -stop(_) -> ok. +-spec start(_) -> ok. +start(_) -> + ok. + +-spec stop(_) -> ok. +stop(_) -> + ok. -spec create_users(escalus:config(), [escalus_users:named_user()]) -> escalus:config(). create_users(Config, Users) -> @@ -220,11 +233,17 @@ delete_users(Config, Users) -> %% escalus_server callbacks %%-------------------------------------------------------------------- -pre_story(Config) -> Config. +-spec pre_story(escalus:config()) -> escalus:config(). +pre_story(Config) -> + Config. -post_story(Config) -> Config. +-spec post_story(escalus:config()) -> escalus:config(). +post_story(Config) -> + Config. -name() -> ?MODULE. +-spec name() -> atom(). +name() -> + ?MODULE. %%-------------------------------------------------------------------- %% Helpers @@ -245,16 +264,20 @@ unregister_user(Config, {_UserName, UserSpec}) -> [U, S, _P] = USP, rpc(ejabberd_admin, unregister, [U, S], 30000). +-spec default_get_remote_sessions() -> any(). default_get_remote_sessions() -> rpc(ejabberd_sm, get_full_session_list, []). +-spec legacy_get_remote_sessions() -> any(). legacy_get_remote_sessions() -> rpc(ejabberd_sm, dirty_get_sessions_list, []). +-spec unify_str_arg(any()) -> any(). unify_str_arg(Arg) -> StrFormat = escalus_ct:get_config(ejabberd_string_format), unify_str_arg(Arg, StrFormat). +-spec unify_str_arg(any(), str | string()) -> any(). unify_str_arg(Arg, str) when is_binary(Arg) -> binary_to_list(Arg); unify_str_arg(Arg, _) -> diff --git a/src/escalus_event.erl b/src/escalus_event.erl index 4cf2e61..156a53c 100644 --- a/src/escalus_event.erl +++ b/src/escalus_event.erl @@ -22,7 +22,6 @@ -include_lib("exml/include/exml.hrl"). --type config() :: escalus_config:config(). -type event_client() :: list({atom(), any()}). -type manager() :: pid(). -type resource() :: binary(). @@ -41,18 +40,23 @@ add_handler(Mgr, Handler, Args) -> delete_handler(Mgr, Handler, Args) -> gen_event:delete_handler(Mgr, Handler, Args). +-spec incoming_stanza(event_client(), exml_stream:element()) -> ok. incoming_stanza(Client, Stanza) -> notify_stanza(Client, incoming_stanza, Stanza). +-spec pop_incoming_stanza(event_client(), exml_stream:element()) -> ok. pop_incoming_stanza(Client, Stanza) -> notify_stanza(Client, pop_incoming_stanza, Stanza). +-spec outgoing_stanza(event_client(), exml_stream:element()) -> ok. outgoing_stanza(Client, Stanza) -> notify_stanza(Client, outgoing_stanza, Stanza). +-spec story_start(escalus_config:config()) -> ok. story_start(Config) -> gen_event:notify(manager(Config), story_start). +-spec story_end(escalus_config:config()) -> ok. story_end(Config) -> gen_event:notify(manager(Config), story_end). @@ -60,21 +64,23 @@ story_end(Config) -> %% ==================================================================== %% @doc Start the event manager -%% @end +-spec start(escalus_config:config()) -> escalus_config:config(). start(Config) -> {ok, Mgr} = gen_event:start_link(), add_handler(Mgr, escalus_history_h, []), [{escalus_event_mgr, Mgr} | Config]. %% @doc Stop the event manager -%% @end +-spec stop(escalus_config:config()) -> escalus_config:config(). stop(Config) -> gen_event:stop(manager(Config)), Config. +-spec get_history(escalus_config:config()) -> [term()]. get_history(Config) -> escalus_history_h:get_history(manager(Config)). +-spec print_history(escalus_config:config()) -> ok. print_history(Config) -> CaseName = proplists:get_value(tc_name, Config), PrivDir = proplists:get_value(priv_dir, Config), @@ -179,7 +185,7 @@ manager(Config) -> %% @doc Create a new event emitter. -spec new_client(Config, User, MaybeResource) -> undefined | EventClient when - Config :: config(), + Config :: escalus_config:config(), User :: escalus_users:user_name() | escalus_users:user_spec(), MaybeResource :: undefined | resource(), EventClient :: event_client(). @@ -206,7 +212,6 @@ new_client_1(Mgr, UserSpec, Resource) -> %% @doc Notify the event system of an event %%

The system accepts any term as the event.

-%% @end notify_stanza(undefined, _, _) -> ok; notify_stanza(Client, EventName, Stanza) -> diff --git a/src/escalus_fresh.erl b/src/escalus_fresh.erl index 7a89423..0e62292 100644 --- a/src/escalus_fresh.erl +++ b/src/escalus_fresh.erl @@ -117,9 +117,12 @@ create_fresh_user(Config, UserName) when is_atom(UserName) -> %%% Stateful API %%% Required if we expect to be able to clean up autogenerated users. +-spec start(escalus:config()) -> ok. start(_Config) -> application:ensure_all_started(worker_pool), ensure_table_present(nasty_global_table()). + +-spec stop(escalus:config()) -> ok | no_return(). stop(_) -> case whereis(nasty_global_table()) of undefined -> @@ -176,6 +179,7 @@ collect_deletion_results(Pending, Failed) -> %%% Internals nasty_global_table() -> escalus_fresh_db. +-spec work_on_deleting_users(term(), {term(), term()}, pid()) -> ok. work_on_deleting_users(Ord, {_Suffix, Conf} = _Item, CollectingPid) -> try do_delete_users(Conf) of _ -> diff --git a/src/escalus_history_h.erl b/src/escalus_history_h.erl index 7109876..c485847 100644 --- a/src/escalus_history_h.erl +++ b/src/escalus_history_h.erl @@ -1,4 +1,5 @@ -module(escalus_history_h). + -behaviour(gen_event). -export([get_history/1]). @@ -7,23 +8,26 @@ terminate/2, handle_info/2, handle_call/2, - handle_event/2, - code_change/3]). + handle_event/2]). -record(state, { events :: list() }). +-type state() :: #state{}. + -spec get_history(escalus_event:manager()) -> list(). get_history(Mgr) -> gen_event:call(Mgr, escalus_history_h, get_history). +-spec init([]) -> {ok, state()}. init([]) -> S = #state{ events = [] }, {ok, S}. +-spec handle_event(term(), state()) -> {ok, state()}. handle_event({incoming_stanza, Jid, Stanza}, State) -> {ok, save_stanza(incoming_stanza, Jid, Stanza, State)}; handle_event({outgoing_stanza, Jid, Stanza}, State) -> @@ -37,15 +41,15 @@ handle_event(story_end, State) -> handle_event(_Event, State) -> {ok, State}. +-spec handle_info(term(), state()) -> {ok, state()}. handle_info(_, State) -> {ok, State}. +-spec handle_call(get_history, state()) -> {ok, list(), state()}. handle_call(get_history, State=#state{events=Events}) -> {ok, lists:reverse(Events), State}. -code_change(_, _, State) -> - {ok, State}. - +-spec terminate(term(), state()) -> ok. terminate(_, _) -> ok. diff --git a/src/escalus_mongooseim.erl b/src/escalus_mongooseim.erl index 4478978..5cdc6a3 100644 --- a/src/escalus_mongooseim.erl +++ b/src/escalus_mongooseim.erl @@ -100,6 +100,7 @@ metric_type(Metric) -> [{_, Type, _} | _] = escalus_ejabberd:rpc(exometer, find_entries, [Metric]), Type. +-spec check_metric_change(T, [T]) -> [T]. check_metric_change({{Metric, {MinChange, MaxChange}}, Before, After}, Acc) -> Change = After - Before, case {Change < MinChange, Change > MaxChange} of diff --git a/src/escalus_new_assert.erl b/src/escalus_new_assert.erl index 9e572f4..1a6af6e 100644 --- a/src/escalus_new_assert.erl +++ b/src/escalus_new_assert.erl @@ -25,18 +25,21 @@ %% API functions %%============================================================================== +-spec assert(atom(), term()) -> ok | no_return(). assert(PredSpec, Arg) -> Fun = predspec_to_fun(PredSpec), StanzaStr = arg_to_list(Arg), assert_true(Fun(Arg), {assertion_failed, assert, PredSpec, Arg, StanzaStr}). +-spec assert(atom(), [term()], term()) -> ok | no_return(). assert(PredSpec, Params, Arg) -> Fun = predspec_to_fun(PredSpec, length(Params) + 1), StanzaStr = arg_to_list(Arg), assert_true(apply(Fun, Params ++ [Arg]), {assertion_failed, assert, PredSpec, Params, Arg, StanzaStr}). +-spec assert_many([atom()], [exml:element()]) -> ok | no_return(). assert_many(Predicates, Stanzas) -> AllStanzas = length(Predicates) == length(Stanzas), Ok = escalus_utils:mix_match(fun predspec_to_fun/1, Predicates, Stanzas), @@ -49,6 +52,7 @@ assert_many(Predicates, Stanzas) -> assert_true(Ok and AllStanzas, {assertion_failed, assert_many, AllStanzas, Predicates, Stanzas, StanzasStr}). +-spec mix_match([atom()], [exml:element()]) -> ok | no_return(). mix_match(Predicates, Stanzas) -> assert_many(Predicates, Stanzas). diff --git a/src/escalus_pred.erl b/src/escalus_pred.erl index 74ac412..e5b042c 100644 --- a/src/escalus_pred.erl +++ b/src/escalus_pred.erl @@ -110,8 +110,11 @@ %% Deprecation support %%-------------------------------------------------------------------- +-spec is_presence_stanza(any()) -> any(). ?DEPRECATED1(is_presence_stanza, is_presence). +-spec is_presence_type(any(), any()) -> any(). ?DEPRECATED2(is_presence_type, is_presence_with_type). +-spec is_result(any()) -> any(). ?DEPRECATED1(is_result, is_iq_result). %%-------------------------------------------------------------------- @@ -307,6 +310,7 @@ is_iq_set(Stanza) -> is_iq(<<"set">>, Stanza). -spec is_iq_get(exml:element()) -> boolean(). is_iq_get(Stanza) -> is_iq(<<"get">>, Stanza). +-spec is_iq_result_or_error(exml:element(), exml:element()) -> boolean(). is_iq_result_or_error(QueryStanza, ResultStanza) -> (is_iq_error(ResultStanza) orelse is_iq_result(ResultStanza)) andalso has_same_id(QueryStanza, ResultStanza). @@ -540,6 +544,7 @@ has_identity(Category, Type, Stanza) -> Idents). %% TODO: Remove as duplicates escalus_assert:has_no_stanzas/1 functionality. +-spec stanza_timeout(tuple()) -> boolean(). stanza_timeout(Arg) -> case element(1, Arg) of 'EXIT' -> @@ -671,8 +676,7 @@ is_bind_result(#xmlel{} = Stanza) -> %% Functors %%-------------------------------------------------------------------- -%% Not supported by erlang 15 :( -%%-spec 'not'( fun((...) -> boolean()) ) -> fun((...) -> boolean()). +-spec 'not'(fun((any()) -> boolean())) -> fun((any()) -> boolean()). 'not'(Pred) when is_function(Pred, 1) -> fun (Arg) -> not Pred(Arg) end; 'not'(Pred) when is_function(Pred, 2) -> diff --git a/src/escalus_pubsub_stanza.erl b/src/escalus_pubsub_stanza.erl index bd8bbe8..06febf0 100644 --- a/src/escalus_pubsub_stanza.erl +++ b/src/escalus_pubsub_stanza.erl @@ -173,10 +173,13 @@ get_user_subscriptions(User, Id, Node) -> end, pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]). +-spec get_subscription_options(escalus_utils:jid_spec(), binary(), {_, _}) -> exml:element(). get_subscription_options(User, Id, {NodeAddr, NodeName}) -> Element = subscription_options(NodeName, User), pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]). +-spec set_subscription_options(escalus_utils:jid_spec(), binary(), {_, _}, [tuple()]) -> + exml:element(). set_subscription_options(User, Id, {NodeAddr, NodeName}, Options) -> FormType = form_type_field_element(<<"subscribe_options">>), EncodedOptions = [FormType | lists:map(fun encode_form_field/1, Options)], @@ -283,22 +286,30 @@ purge_all_items(User, Id, {NodeAddr, NodeName}) -> %% Whole stanzas +-spec iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). iq(Type, From, Id, pep, Elements) -> Stanza = escalus_stanza:iq(Type, Elements), StanzaWithId = escalus_stanza:set_id(Stanza, Id), - escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From)); + escalus_stanza:from(StanzaWithId, From); iq(Type, From, Id, To, Elements) -> Stanza = escalus_stanza:iq(To, Type, Elements), StanzaWithId = escalus_stanza:set_id(Stanza, Id), - escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From)). + escalus_stanza:from(StanzaWithId, From). +-spec pubsub_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). pubsub_iq(Type, User, Id, NodeAddr, Elements) -> pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB). +-spec pubsub_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()], binary()) -> exml:element(). pubsub_iq(Type, User, Id, NodeAddr, Elements, NS) -> PubSubElement = pubsub_element(Elements, NS), iq(Type, User, Id, NodeAddr, [PubSubElement]). +-spec pubsub_owner_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). pubsub_owner_iq(Type, User, Id, NodeAddr, Elements) -> pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB_OWNER). @@ -322,6 +333,7 @@ optional_form(FormName, NodeName, Type, Fields) -> create_node_element(NodeName) -> #xmlel{name = <<"create">>, attrs = #{<<"node">> => NodeName}}. +-spec pubsub_element([exml:cdata() | exml:element()], binary()) -> exml:element(). pubsub_element(Children, NS) -> #xmlel{name = <<"pubsub">>, attrs = #{<<"xmlns">> => NS}, diff --git a/src/escalus_rpc.erl b/src/escalus_rpc.erl index ab112c4..ebcb3dd 100644 --- a/src/escalus_rpc.erl +++ b/src/escalus_rpc.erl @@ -19,6 +19,7 @@ %% in a concurrent environment as it gets/sets the cookie %% with `erlang:get_cookie/0' and `erlang:set_cookie/1'. %% Interleaving these calls in concurrent processes is prone to race conditions. +-spec call(node(), module(), atom(), [term()], timeout(), atom()) -> term(). call(Node, Module, Function, Args, TimeOut, Cookie) -> call_with_cookie_match(Node, Module, Function, Args, TimeOut, Cookie). @@ -43,5 +44,5 @@ set_the_cookie([]) -> []; set_the_cookie(Cookie) -> Cookie0 = erlang:get_cookie(), - erlang:set_cookie(node(),Cookie), + erlang:set_cookie(node(), Cookie), Cookie0. diff --git a/src/escalus_server.erl b/src/escalus_server.erl index 47cc0b3..4bb0d39 100644 --- a/src/escalus_server.erl +++ b/src/escalus_server.erl @@ -33,9 +33,11 @@ pre_story(Config) -> post_story(Config) -> call_server(get_server(Config), post_story, [Config]). +-spec name(escalus:config()) -> any(). name(Config) -> call_server(get_server(Config), name, []). +-spec get_server(escalus:config()) -> any(). get_server(Config) -> escalus_config:get_config(escalus_xmpp_server, Config, undefined). diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index ce6b358..a2bdf03 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -86,7 +86,7 @@ search_iq/2]). %% XEP-0280: Message Carbons --export([carbons_disable/0,carbons_enable/0]). +-export([carbons_disable/0, carbons_enable/0]). %% XEP-0313: Message Archive Management -export([mam_archive_query/1, @@ -148,6 +148,7 @@ %% Stream - related functions %%-------------------------------------------------------------------- +-spec stream_start(binary(), binary()) -> exml_stream:start(). stream_start(Server, XMLNS) -> #xmlstreamstart{name = <<"stream:stream">>, attrs = #{<<"to">> => Server, @@ -156,43 +157,51 @@ stream_start(Server, XMLNS) -> <<"xmlns">> => XMLNS, <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>}}. +-spec stream_end() -> exml_stream:stop(). stream_end() -> #xmlstreamend{name = <<"stream:stream">>}. +-spec ws_open(binary()) -> exml:element(). ws_open(Server) -> #xmlel{name= <<"open">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>, <<"to">> => Server, <<"version">> => <<"1.0">>}}. -ws_close()-> +-spec ws_close() -> exml:element(). +ws_close() -> #xmlel{name= <<"close">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>}}. +-spec starttls() -> exml:element(). starttls() -> #xmlel{name = <<"starttls">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-tls">>}}. +-spec compress(binary()) -> exml:element(). compress(Method) -> #xmlel{name = <<"compress">>, attrs = #{<<"xmlns">> => <<"http://jabber.org/protocol/compress">>}, children = [#xmlel{name = <<"method">>, children = [#xmlcdata{content = Method}]}]}. --spec iq(binary(), [exml:element()]) -> exml:element(). +-spec iq(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq(Type, Body) -> #xmlel{name = <<"iq">>, attrs = #{<<"type">> => Type, <<"id">> => id()}, children = Body}. +-spec iq(escalus_utils:jid_spec(), binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq(To, Type, Body) -> IQ = iq(Type, Body), - IQ#xmlel{attrs = maps:put(<<"to">>, To, IQ#xmlel.attrs)}. + to(IQ, To). %% slightly naughty, this isn't a stanza but it will go inside an +-spec query_el(binary(), [exml:cdata() | exml:element()]) -> exml:element(). query_el(NS, Children) -> query_el(NS, #{}, Children). +-spec query_el(binary(), exml:attrs(), [exml:cdata() | exml:element()]) -> exml:element(). query_el(NS, Attrs, Children) -> #xmlel{name = <<"query">>, attrs = maps:put(<<"xmlns">>, NS, Attrs), @@ -200,6 +209,7 @@ query_el(NS, Attrs, Children) -> %% http://xmpp.org/extensions/xep-0004.html %% slightly naughty - this isn't a stanza but can be a child of various stanza types +-spec x_data_form(binary(), [exml:cdata() | exml:element()]) -> exml:element(). x_data_form(Type, Children) -> #xmlel{name = <<"x">>, attrs = #{<<"xmlns">> => ?NS_DATA_FORMS, <<"type">> => Type}, @@ -223,27 +233,33 @@ session() -> iq(<<"set">>, [#xmlel{name = <<"session">>, attrs = #{<<"xmlns">> => NS}}]). +-spec to(exml:element(), escalus_utils:jid_spec()) -> exml:element(). to(Stanza, Recipient) when is_binary(Recipient) -> setattr(Stanza, <<"to">>, Recipient); to(Stanza, Recipient) -> setattr(Stanza, <<"to">>, escalus_utils:get_jid(Recipient)). +-spec from(exml:element(), escalus_utils:jid_spec()) -> exml:element(). from(Stanza, Recipient) when is_binary(Recipient) -> setattr(Stanza, <<"from">>, Recipient); from(Stanza, Recipient) -> setattr(Stanza, <<"from">>, escalus_utils:get_jid(Recipient)). +-spec set_id(exml:element(), binary()) -> exml:element(). set_id(Stanza, ID) -> setattr(Stanza, <<"id">>, ID). +-spec setattr(exml:element(), binary(), binary()) -> exml:element(). setattr(Stanza, Key, Val) -> NewAttrs = maps:put(Key, Val, Stanza#xmlel.attrs), Stanza#xmlel{attrs = NewAttrs}. +-spec tags([{binary(), binary()}]) -> [exml:element()]. tags(KVs) -> [#xmlel{name = K, children = [#xmlcdata{content = V}]} || {K, V} <- KVs]. +-spec presence(binary()) -> exml:element(). presence(Type) -> presence(Type, []). @@ -255,9 +271,12 @@ presence(Type, Children) -> attrs = #{<<"type">> => bin(Type)}, children = Children}. +-spec presence_direct(escalus_utils:jid_spec(), binary()) -> exml:element(). presence_direct(Recipient, Type) -> presence_direct(Recipient, Type, []). +-spec presence_direct( + escalus_utils:jid_spec(), binary(), [exml:element() | exml:cdata()]) -> exml:element(). presence_direct(#client{} = Recipient, Type, Body) -> %% FIXME: this clause is only for backwards compatibility, %% remove at some point @@ -276,11 +295,13 @@ presence_direct(#client{} = Recipient, Type, Body) -> presence_direct(Recipient, Type, Body) -> to(presence(Type, Body), Recipient). +-spec presence_show(binary()) -> exml:element(). presence_show(Show) -> presence(<<"available">>, [#xmlel{name = <<"show">>, children = [#xmlcdata{content = Show}]}]). +-spec error_element(binary(), binary()) -> exml:element(). error_element(Type, Condition) -> #xmlel{name = <<"error">>, attrs = #{<<"type">> => Type}, @@ -325,15 +346,18 @@ message(Text, Attrs) -> children = [#xmlcdata{content = Text}]}]}, Attrs). +-spec chat_to(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_to(Recipient, Msg) -> message(Msg, #{type => <<"chat">>, to => Recipient}). +-spec chat(escalus_utils:jid_spec(), escalus_utils:jid_spec(), binary()) -> exml:element(). chat(Sender, Recipient, Msg) -> message(Msg, #{type => <<"chat">>, from => Sender, to => Recipient}). +-spec chat_to_short_jid(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_to_short_jid(Recipient, Msg) -> message(Msg, #{type => <<"chat">>, to => escalus_utils:get_short_jid(Recipient)}). @@ -351,12 +375,14 @@ chat_to_with_id_and_timestamp(Recipient, Msg) -> id => uuid_v4(), timestamp => integer_to_binary(os:system_time(microsecond))}). +-spec chat_without_carbon_to(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_without_carbon_to(Recipient, Msg) -> Stanza = #xmlel{children = Children} = chat_to(Recipient, Msg), Stanza#xmlel{children = Children ++ [#xmlel{name = <<"private">>, attrs = #{<<"xmlns">> => ?NS_CARBONS_2}}]}. +-spec receipt_req(exml:element()) -> exml:element(). receipt_req(#xmlel{name = <<"message">>, attrs = Attrs, children = Children } = Msg) -> ReqStanza = receipt_req_elem(), Msg2 = case maps:find(<<"id">>, Attrs) of @@ -367,6 +393,7 @@ receipt_req(#xmlel{name = <<"message">>, attrs = Attrs, children = Children } = end, Msg2#xmlel{ children = [ReqStanza | Children] }. +-spec receipt_conf(exml:element()) -> exml:element(). receipt_conf(#xmlel{attrs = #{<<"id">> := ID, <<"from">> := From} = Attrs, children = Children}) -> Type = case maps:find(<<"type">>, Attrs) of error -> <<"chat">>; @@ -405,26 +432,32 @@ receipt_conf_elem(ID) -> children = [] }. +-spec groupchat_to(escalus_utils:jid_spec(), binary()) -> exml:element(). groupchat_to(Recipient, Msg) -> message(undefined, Recipient, <<"groupchat">>, Msg). +-spec get_registration_fields() -> exml:element(). get_registration_fields() -> iq(<<"get">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}}]). +-spec register_account([exml:cdata() | exml:element()]) -> exml:element(). register_account(Body) -> iq(<<"set">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = Body}]). +-spec remove_account() -> exml:element(). remove_account() -> iq(<<"set">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = [#xmlel{name = <<"remove">>}]}]). +-spec iq_result(exml:element()) -> exml:element(). iq_result(Request) -> iq_result(Request, []). +-spec iq_result(exml:element(), [exml:cdata() | exml:element()]) -> exml:element(). iq_result(Request, Payload) -> ToAttr = case exml_query:attr(Request, <<"from">>) of undefined -> @@ -437,16 +470,20 @@ iq_result(Request, Payload) -> attrs = ToAttr#{<<"id">> => Id, <<"type">> => <<"result">>}, children = Payload}. +-spec iq_get(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_get(NS, Payload) -> iq_with_type(<<"get">>, NS, Payload). +-spec iq_set(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_set(NS, Payload) -> iq_with_type(<<"set">>, NS, Payload). +-spec iq_set_nonquery(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_set_nonquery(NS, Payload) -> %% Don't wrap payload with iq_with_type(<<"set">>, NS, Payload, nonquery). +-spec iq_with_type(binary(), binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_with_type(Type, NS, Payload) -> iq(Type, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => NS}, @@ -459,14 +496,17 @@ iq_with_type(Type, NS, Payload, nonquery) -> <<"id">> => id()}, children = Payload}. +-spec roster_get() -> exml:element(). roster_get() -> iq_get(?NS_ROSTER, []). +-spec roster_get(binary()) -> exml:element(). roster_get(Ver) -> #xmlel{children = [Query]} = Stanza = iq_get(?NS_ROSTER, []), NewQuery = Query#xmlel{attrs = maps:put(<<"ver">>, Ver, Query#xmlel.attrs)}, Stanza#xmlel{children = [NewQuery]}. +-spec roster_add_contacts([{escalus_utils:jid_spec(), list(), binary()}]) -> exml:element(). roster_add_contacts(ItemSpecs) -> iq_set(?NS_ROSTER, lists:map(fun contact_item/1, ItemSpecs)). @@ -481,47 +521,59 @@ contact_item({User, Groups, Nick}) -> children = [#xmlcdata{content = bin(Group)}]} || Group <- Groups]}. +-spec roster_add_contact(binary(), [binary()], binary()) -> exml:element(). roster_add_contact(User, Groups, Nick) -> roster_add_contacts([{User, Groups, Nick}]). %% FIXME: see contact_item/1 comment +-spec roster_remove_contact(escalus_utils:jid_spec()) -> exml:element(). roster_remove_contact(User) -> iq_set(?NS_ROSTER, [#xmlel{name = <<"item">>, attrs = #{<<"jid">> => escalus_utils:get_short_jid(User), <<"subscription">> => <<"remove">>}}]). +-spec private_set(exml:element()) -> exml:element(). private_set(Element) -> iq_set(?NS_PRIVATE, [Element]). +-spec private_get(term(), term()) -> exml:element(). private_get(NS, Name) -> Element = #xmlel{name = bin(Name), attrs = #{<<"xmlns">> => bin(NS)}}, iq_get(?NS_PRIVATE, [Element]). +-spec last_activity(escalus_utils:jid_spec()) -> exml:element(). last_activity(User) -> to(iq_get(?NS_LAST_ACTIVITY, []), User). +-spec privacy_get_all() -> exml:element(). privacy_get_all() -> iq_get(?NS_PRIVACY, []). +-spec privacy_get_lists([term()]) -> exml:element(). privacy_get_lists(ListNames) -> iq_get(?NS_PRIVACY, [#xmlel{name = <<"list">>, attrs = #{<<"name">> => bin(Name)}} || Name <- ListNames]). +-spec privacy_set_list(exml:element()) -> exml:element(). privacy_set_list(PrivacyList) -> iq_set(?NS_PRIVACY, [PrivacyList]). +-spec privacy_activate(term()) -> exml:element(). privacy_activate(ListName) -> privacy_set(<<"active">>, #{<<"name">> => bin(ListName)}). +-spec privacy_deactivate() -> exml:element(). privacy_deactivate()-> privacy_set(<<"active">>, #{}). +-spec privacy_set_default(term()) -> exml:element(). privacy_set_default(ListName) -> privacy_set(<<"default">>, #{<<"name">> => bin(ListName)}). +-spec privacy_no_default() -> exml:element(). privacy_no_default()-> privacy_set(<<"default">>, #{}). @@ -529,17 +581,20 @@ privacy_set(What, Attrs) -> iq_set(?NS_PRIVACY, [#xmlel{name = What, attrs = Attrs}]). %% Create empty list element with given name. +-spec privacy_list(binary(), [exml:cdata() | exml:element()]) -> exml:element(). privacy_list(Name, Items) -> #xmlel{name = <<"list">>, attrs = #{<<"name">> => Name}, children = Items}. +-spec privacy_list_item(binary(), binary(), [binary()]) -> exml:element(). privacy_list_item(Order, Action, Content) -> #xmlel{name = <<"item">>, attrs = #{<<"order">> => Order, <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. +-spec privacy_list_item(binary(), binary(), binary(), binary(), [binary()]) -> exml:element(). privacy_list_item(Order, Action, Type, Value, Content) -> #xmlel{name = <<"item">>, attrs = #{<<"order">> => Order, @@ -548,25 +603,33 @@ privacy_list_item(Order, Action, Type, Value, Content) -> <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. +-spec privacy_list_jid_item(binary(), binary(), escalus_utils:jid_spec(), [binary()]) -> + exml:element(). privacy_list_jid_item(Order, Action, Who, Contents) -> privacy_list_item(Order, Action, <<"jid">>, escalus_utils:get_jid(Who), Contents). +-spec disco_info(escalus_utils:jid_spec()) -> exml:element(). disco_info(JID) -> Query = query_el(?NS_DISCO_INFO, []), iq(JID, <<"get">>, [Query]). +-spec disco_info(escalus_utils:jid_spec(), binary()) -> exml:element(). disco_info(JID, Node) -> Query = query_el(?NS_DISCO_INFO, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [Query]). +-spec disco_items(escalus_utils:jid_spec()) -> exml:element(). disco_items(JID) -> ItemsQuery = query_el(?NS_DISCO_ITEMS, []), iq(JID, <<"get">>, [ItemsQuery]). + +-spec disco_items(escalus_utils:jid_spec(), binary()) -> exml:element(). disco_items(JID, Node) -> ItemsQuery = query_el(?NS_DISCO_ITEMS, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [ItemsQuery]). +-spec search_fields([null | {binary(), binary()} | term()]) -> [exml:element()]. search_fields([]) -> []; search_fields([null|Rest]) -> @@ -578,24 +641,30 @@ search_fields([{Key, Val}|Rest]) -> children = [#xmlcdata{content = Val}]}]} | search_fields(Rest)]. +-spec search_fields_iq(escalus_utils:jid_spec()) -> exml:element(). search_fields_iq(JID) -> iq(JID, <<"get">>, [ query_el(?NS_SEARCH, [])]). +-spec search_iq(escalus_utils:jid_spec(), [exml:cdata() | exml:element()]) -> exml:element(). search_iq(JID, Fields) -> Form = x_data_form(<<"submit">>, Fields), Query = query_el(?NS_SEARCH, [Form]), iq(JID, <<"set">>, [Query]). +-spec vcard_request() -> exml:element(). vcard_request() -> iq(<<"get">>, [vcard([])]). +-spec vcard_request(binary()) -> exml:element(). vcard_request(JID) -> iq(JID, <<"get">>, [vcard([])]). +-spec vcard_update(list()) -> exml:element(). vcard_update(Fields) -> iq(<<"set">>, [vcard(Fields)]). +-spec vcard_update(binary(), list()) -> exml:element(). vcard_update(JID, Fields) -> iq(JID, <<"set">>, [vcard(Fields)]). @@ -620,9 +689,11 @@ tuples_to_fields([{Name, Children}|Rest]) when is_list(Children) -> [field(Name, tuples_to_fields(Children)) | tuples_to_fields(Rest)]. +-spec adhoc_request(binary()) -> exml:element(). adhoc_request(Node) -> adhoc_request(Node, []). +-spec adhoc_request(binary(), [exml:cdata() | exml:element()]) -> exml:element(). adhoc_request(Node, Payload) -> iq(<<"set">>, [#xmlel{name = <<"command">>, attrs = #{<<"xmlns">> => ?NS_ADHOC, @@ -630,6 +701,7 @@ adhoc_request(Node, Payload) -> <<"action">> => <<"execute">>}, children = Payload}]). +-spec ping_request(escalus_utils:jid_spec()) -> exml:element(). ping_request(To) -> IQ = iq(<<"get">>, [#xmlel{name = <<"ping">>, attrs = #{<<"xmlns">> => ?NS_PING} @@ -637,34 +709,38 @@ ping_request(To) -> to(IQ, To). --spec service_discovery(binary()) -> #xmlel{}. +-spec service_discovery(binary()) -> exml:element(). service_discovery(Server) -> escalus_stanza:setattr(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), <<"to">>, Server). --spec auth(binary()) -> #xmlel{}. +-spec auth(binary()) -> exml:element(). auth(Mechanism) -> auth(Mechanism, []). --spec auth(binary(), [#xmlcdata{}]) -> #xmlel{}. +-spec auth(binary(), [exml:cdata() | exml:element()]) -> exml:element(). auth(Mechanism, Children) -> #xmlel{name = <<"auth">>, attrs = #{<<"xmlns">> => ?NS_SASL, <<"mechanism">> => Mechanism}, children = Children}. +-spec auth_response() -> exml:element(). auth_response() -> auth_response([]). +-spec auth_response([exml:cdata() | exml:element()]) -> exml:element(). auth_response(Children) -> #xmlel{name = <<"response">>, attrs = #{<<"xmlns">> => ?NS_SASL}, children = Children}. +-spec enable_sm() -> exml:element(). enable_sm() -> #xmlel{name = <<"enable">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. +-spec enable_sm(proplists:proplist()) -> exml:element(). enable_sm(Opts) -> case proplists:is_defined(resume, Opts) of true -> @@ -674,15 +750,18 @@ enable_sm(Opts) -> enable_sm() end. +-spec sm_request() -> exml:element(). sm_request() -> #xmlel{name = <<"r">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. +-spec sm_ack(integer()) -> exml:element(). sm_ack(H) -> #xmlel{name = <<"a">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, <<"h">> => integer_to_binary(H)}}. +-spec resume(binary(), integer()) -> exml:element(). resume(SMID, PrevH) -> #xmlel{name = <<"resume">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, @@ -695,11 +774,11 @@ resume(SMID, PrevH) -> %% @TODO: move the stanza constructors from %% tests/mam_SUITE.erl into here. -spec field_el(binary(), binary(), undefined | binary() | [binary()]) -> - exml:element(). + undefined | exml:element(). field_el(_Name, _Type, undefined) -> undefined; field_el(Name, Type, Values) when is_list(Values) -> - Fields = lists:map(fun (E) -> + Fields = lists:map(fun(E) -> #xmlel{name = <<"value">>, children = [#xmlcdata{content = E}]} end, Values), @@ -762,8 +841,8 @@ mam_lookup_messages_iq(QueryId, Start, End, WithJID, DirectionWMessageId) -> -spec mam_lookup_messages_iq(binary(), binary(), binary(), binary(), term(), boolean()) -> exml:element(). mam_lookup_messages_iq(QueryId, Start, End, WithJID, DirectionWMessageId, Simple) -> - IQ = #xmlel{children=[Q]} = mam_lookup_messages_iq(QueryId, Start, End, - WithJID, Simple), + IQ = #xmlel{children = [Q]} = mam_lookup_messages_iq(QueryId, Start, End, + WithJID, Simple), RSM = defined([fmapM(fun rsm_after_or_before/1, DirectionWMessageId)]), Other = Q#xmlel.children, Q2 = Q#xmlel{children = Other ++ RSM}, @@ -797,9 +876,11 @@ max(_) -> %% XEP-0280 Carbons %% +-spec carbons_enable() -> exml:element(). carbons_enable() -> iq_set_nonquery(?NS_JABBER_CLIENT, [enable_carbons_el()]). +-spec carbons_disable() -> exml:element(). carbons_disable() -> iq_set_nonquery(?NS_JABBER_CLIENT, [disable_carbons_el()]). diff --git a/src/escalus_story.erl b/src/escalus_story.erl index 196c895..4752507 100644 --- a/src/escalus_story.erl +++ b/src/escalus_story.erl @@ -39,6 +39,7 @@ %% end). %% %% @end +-spec story(escalus:config(), term(), fun()) -> term(). story(ConfigIn, ResourceCounts, Story) -> story(ConfigIn, ResourceCounts, Story, []). @@ -59,9 +60,11 @@ story(ConfigIn, ResourceCounts, Story) -> %% See carboncopy_SUITE test properties for an example: %% https://github.com/esl/MongooseIM/blob/f0ed90a93e17f7f3d5baf4d51bbb3f8b19826dd8/test.disabled/ejabberd_tests/tests/carboncopy_SUITE.erl#L183-L185 %% @end +-spec story_with_client_list(escalus:config(), term(), fun()) -> term(). story_with_client_list(ConfigIn, ResourceCounts, Story) -> story(ConfigIn, ResourceCounts, Story, [clients_as_list]). +-spec story(escalus:config(), term(), fun(), proplists:proplist()) -> term(). story(ConfigIn, ResourceCounts, Story, Opts) -> ClientDescs = clients_from_resource_counts(ConfigIn, ResourceCounts), try @@ -86,6 +89,7 @@ make_everyone_friends(Config) -> Users = escalus_config:get_config(escalus_users, Config), make_everyone_friends(Config, Users). +-spec make_everyone_friends(escalus:config(), [{_, _}]) -> escalus:config(). make_everyone_friends(Config0, Users) -> % start the clients Config1 = escalus_cleaner:start(Config0), @@ -124,6 +128,7 @@ call_start_ready_clients(Config, UserCDs) -> escalus_overridables:do(Config, start_ready_clients, [Config, UserCDs], {?MODULE, start_ready_clients}). +-spec start_ready_clients(escalus:config(), [term()]) -> [escalus:client()]. start_ready_clients(Config, FlatCDs) -> {_, RClients} = lists:foldl(fun({UserSpec, BaseResource}, {N, Acc}) -> Resource = escalus_overridables:do(Config, modify_resource, [BaseResource], @@ -144,6 +149,7 @@ start_ready_clients(Config, FlatCDs) -> ensure_all_clean(Clients), Clients. +-spec send_initial_presence(escalus:client()) -> ok. send_initial_presence(Client) -> escalus_client:send(Client, escalus_stanza:presence(<<"available">>)). diff --git a/src/escalus_sup.erl b/src/escalus_sup.erl index 115e2fc..3ca7576 100644 --- a/src/escalus_sup.erl +++ b/src/escalus_sup.erl @@ -24,13 +24,11 @@ %% Supervisor callbacks -export([init/1]). -%% Helper macro for declaring children of supervisor --define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). - %% =================================================================== %% API functions %% =================================================================== +-spec start_link() -> supervisor:startlink_ret(). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -38,6 +36,7 @@ start_link() -> %% Supervisor callbacks %% =================================================================== +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. init([]) -> {ok, { {one_for_one, 5, 10}, []} }. diff --git a/src/escalus_utils.erl b/src/escalus_utils.erl index 5595bb9..369bb3a 100644 --- a/src/escalus_utils.erl +++ b/src/escalus_utils.erl @@ -46,16 +46,17 @@ -type jid_spec() :: #client{} | atom() | binary() | string(). -export_type([jid_spec/0]). --spec log_stanzas(iolist(), [#xmlel{}]) -> any(). +-spec log_stanzas(iolist(), [exml:element()]) -> any(). log_stanzas(Comment, Stanzas) -> error_logger:info_msg("~s:~s~n", [Comment, stanza_lines("\n * ", Stanzas)]). --spec pretty_stanza_list([#xmlel{}]) -> string(). +-spec pretty_stanza_list([exml:element()]) -> string(). pretty_stanza_list(Stanzas) -> binary_to_list(list_to_binary(stanza_lines(" ", Stanzas))). %% calls Fun(A, B) on each distinct (A =/= B) pair of elements in List %% if Fun(A, B) was called, then Fun(B, A) won't +-spec distinct_pairs(fun((T, T) -> any()), [T]) -> integer(). distinct_pairs(Fun, List) -> K = each_with_index(fun(A, N) -> each_with_index(fun(B, M) -> @@ -69,6 +70,7 @@ distinct_pairs(Fun, List) -> K * (K - 1) div 2. %% calls Fun(A, B) on each distinct (A =/= B) ordered pair of elements in List +-spec distinct_ordered_pairs(fun((T, T) -> any()), [T]) -> integer(). distinct_ordered_pairs(Fun, List) -> K = each_with_index(fun(A, N) -> each_with_index(fun(B, M) -> @@ -82,29 +84,35 @@ distinct_ordered_pairs(Fun, List) -> K * (K - 1). %% Calls Fun(Element, Index) for indices (starting from Start) and elements of List +-spec each_with_index(fun((T, integer()) -> any()), _, [T]) -> integer(). each_with_index(Fun, Start, List) -> lists:foldl(fun(Element, N) -> Fun(Element, N), N + 1 end, Start, List). +-spec all_true([boolean()]) -> boolean(). all_true(List) -> lists:foldl(fun erlang:'and'/2, true, List). +-spec any_true([boolean()]) -> boolean(). any_true(List) -> lists:foldl(fun erlang:'or'/2, false, List). +-spec identity(A) -> A. identity(X) -> X. %% Does for each Case in Cases exist a Cond in Conds such that %% (Predgen(Cond))(Case) == true? +-spec mix_match(fun((A) -> fun((B) -> boolean())), [A], [B]) -> boolean(). mix_match(Predgen, Conds, Cases) -> [] == lists:foldl(fun(Cond, CasesLeft) -> Pred = Predgen(Cond), drop_first_such(Pred, CasesLeft) end, Cases, Conds). +-spec drop_first_such(fun((A) -> boolean()), [A]) -> [A]. drop_first_such(Pred, List) -> drop_first_such(Pred, List, []). @@ -113,7 +121,7 @@ drop_first_such(_, [], Acc) -> drop_first_such(Pred, [H|T], Acc) -> case Pred(H) of true -> - lists:reverse(Acc) ++ T; + lists:reverse(Acc, T); false -> drop_first_such(Pred, T, [H|Acc]) end. @@ -121,6 +129,7 @@ drop_first_such(Pred, [H|T], Acc) -> stanza_lines(Prefix, Stanzas) -> [[Prefix, exml:to_iolist(S)] || S <- Stanzas]. +-spec show_backtrace() -> any(). show_backtrace() -> try throw(catch_me) catch _:_:S -> @@ -170,13 +179,16 @@ get_server(UserOrClient) -> get_resource(JID) -> regexp_get(JID, <<"^[^/]+/(.*)$">>). +-spec is_prefix(binary(), binary()) -> boolean(). is_prefix(Prefix, Full) when is_binary(Prefix), is_binary(Full) -> LCP = binary:longest_common_prefix([Prefix, Full]), size(Prefix) =< size(Full) andalso LCP == size(Prefix). +-spec start_clients([{_, _}]) -> {pid(), list(pid())}. start_clients(Clients) -> start_clients([], Clients). +-spec start_clients(escalus_config:config(), [{_, _}]) -> {pid(), list(pid())}. start_clients(Config0, ClientRecipes) -> AllCDs = escalus_config:get_config(escalus_users, Config0), FlatCDs = [{CD, Res} || {Username, Resources} <- ClientRecipes, @@ -188,6 +200,7 @@ start_clients(Config0, ClientRecipes) -> {escalus_story, start_ready_clients}), {Cleaner, Clients}. +-spec regexp_get(binary(), binary()) -> binary(). regexp_get(Jid, Regex) -> {match, [ShortJid]} = re:run(Jid, Regex, [{capture, all_but_first, binary}]), From b28baf538bc4d88e26eb3b39b11cb1f13ebd4ea7 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 20 Jan 2025 14:57:24 +0100 Subject: [PATCH 15/23] Fix running dialyzer in 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 13d9e04..97e79f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,4 +29,4 @@ jobs: - run: make test - run: make ct - run: make dialyzer - if: ${{ matrix.otp == '27' }} + if: ${{ matrix.otp_vsn == '27' }} From 662a480d7a034e20b03196966674085a10c7add6 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 20 Jan 2025 15:02:25 +0100 Subject: [PATCH 16/23] Fix dialyzer public_key error --- src/escalus.app.src | 1 + 1 file changed, 1 insertion(+) diff --git a/src/escalus.app.src b/src/escalus.app.src index e43c97e..dd87cd5 100644 --- a/src/escalus.app.src +++ b/src/escalus.app.src @@ -6,6 +6,7 @@ {applications, [ kernel, stdlib, + public_key, ssl, exml, gun, From e356e9274164759453edd7dd100ca915e113ceba Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:03:27 +0100 Subject: [PATCH 17/23] Whitespace --- src/escalus_tcp.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl index c0bc0aa..052077e 100644 --- a/src/escalus_tcp.erl +++ b/src/escalus_tcp.erl @@ -57,7 +57,6 @@ -export_type([sm_state/0]). -define(WAIT_FOR_SOCKET_CLOSE_TIMEOUT, 1000). --define(SERVER, ?MODULE). -include("escalus_tcp.hrl"). -type state() :: #state{}. @@ -254,7 +253,7 @@ handle_call(get_ssl, _From, #state{ssl = false} = State) -> handle_call(get_ssl, _From, #state{ssl = _} = State) -> {reply, true, State}; handle_call({set_active, Active}, _From, State) -> - {reply, ok, set_active_opt(State,Active)}; + {reply, ok, set_active_opt(State, Active)}; handle_call({set_filter_pred, Pred}, _From, State) -> {reply, ok, State#state{filter_pred = Pred}}; handle_call(get_tls_last_message, _From, From 10e0a955bbcfd5f1593c0e3a2103341b695895f1 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:02:14 +0100 Subject: [PATCH 18/23] Remove fast_tls dependency --- rebar.config | 1 - rebar.lock | 6 ------ src/escalus.app.src | 1 - 3 files changed, 8 deletions(-) diff --git a/rebar.config b/rebar.config index 9b7dede..452b9a4 100644 --- a/rebar.config +++ b/rebar.config @@ -12,7 +12,6 @@ {uuid, "2.0.7", {pkg, uuid_erl}}, {gun, "2.1.0"}, {worker_pool, "6.4.0"}, - {fast_tls, "1.1.21"}, {fast_scram, "0.6.1"} ]}. diff --git a/rebar.lock b/rebar.lock index f52ca89..95c786a 100644 --- a/rebar.lock +++ b/rebar.lock @@ -4,10 +4,8 @@ {<<"exml">>,{pkg,<<"hexml">>,<<"4.0.0">>},0}, {<<"fast_pbkdf2">>,{pkg,<<"fast_pbkdf2">>,<<"1.0.6">>},1}, {<<"fast_scram">>,{pkg,<<"fast_scram">>,<<"0.6.1">>},0}, - {<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.21">>},0}, {<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0}, {<<"meck">>,{pkg,<<"meck">>,<<"1.0.0">>},0}, - {<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.26">>},1}, {<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.7">>},1}, {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.7">>},0}, {<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"6.4.0">>},0}]}. @@ -18,10 +16,8 @@ {<<"exml">>, <<"54C1AAD5BD290EC31C19CE4A5D449C7E3236107AA2D3610FC04FC963DC8EAF13">>}, {<<"fast_pbkdf2">>, <<"199BCEC73A1A246941E9465D3DC41052953B638128841ED24B29ED03CF70AF27">>}, {<<"fast_scram">>, <<"BEEADB03D774640F0671681759CE53B2FF33CB58C86FD9BF2A793E2FC1ED0F5D">>}, - {<<"fast_tls">>, <<"65D7D547A09EEFB37A1C0D04D8601FAC4F3E6E2C1EDE859A7787081670F9648D">>}, {<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>}, {<<"meck">>, <<"24676CB6EE6951530093A93EDCD410CFE4CB59FE89444B875D35C9D3909A15D0">>}, - {<<"p1_utils">>, <<"67B0C4AC9FA3BA3EF563B31AA111B0A004439A37FAC85E027F1C3617E1C7EC6C">>}, {<<"quickrand">>, <<"D2BD76676A446E6A058D678444B7FDA1387B813710D1AF6D6E29BB92186C8820">>}, {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}, {<<"worker_pool">>, <<"0347B805A8E5804B5676A9885FB3B9B6C1627099C449C3C67C0E8E6AF79E9AA6">>}]}, @@ -31,10 +27,8 @@ {<<"exml">>, <<"08CC97527C708D57A03F467049AC260B5951BD67906AA154BE56B5D8BDD3238C">>}, {<<"fast_pbkdf2">>, <<"35EEC22629AAA739915843C7B7DE0D84657D1ECE972D8BBC86368747E9C14012">>}, {<<"fast_scram">>, <<"FE0650A309FDF97C75E1EA812CCFB40EB464ECAFD3783E83AA17C7F572EDAB0B">>}, - {<<"fast_tls">>, <<"131542913937025E48CD80AA81F00359686D5501B75621E72026A87B5229505B">>}, {<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>}, {<<"meck">>, <<"680A9BCFE52764350BEB9FB0335FB75FEE8E7329821416CEE0A19FEC35433882">>}, - {<<"p1_utils">>, <<"D0379E8C1156B98BD64F8129C1DE022FCCA4F2FDB7486CE73BF0ED2C3376B04C">>}, {<<"quickrand">>, <<"B8ACBF89A224BC217C3070CA8BEBC6EB236DBE7F9767993B274084EA044D35F0">>}, {<<"uuid">>, <<"4E4C5CA3461DC47C5E157ED42AA3981A053B7A186792AF972A27B14A9489324E">>}, {<<"worker_pool">>, <<"59946FBCE1D331CDEB153EDD36A823DC1AAB4C2482662582B983C9C90EBC3461">>}]} diff --git a/src/escalus.app.src b/src/escalus.app.src index dd87cd5..043c052 100644 --- a/src/escalus.app.src +++ b/src/escalus.app.src @@ -13,7 +13,6 @@ meck, bbmustache, uuid, - fast_tls, fast_scram, worker_pool ]}, From 2c2b5373054f3628c47c7599e9d9bb6c84a9ba27 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:03:12 +0100 Subject: [PATCH 19/23] Remove fast_tls logic --- src/escalus_auth.erl | 13 ++---- src/escalus_tcp.erl | 98 +++++++++++++------------------------------- src/escalus_tcp.hrl | 3 +- 3 files changed, 33 insertions(+), 81 deletions(-) diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index ab850d5..a600337 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -127,8 +127,7 @@ auth_sasl_scram(#{plus_variant := PlusVariant, Conn, Props) -> Username = get_property(username, Props), Password = get_property(password, Props), - ChannelBinding = scram_sha_auth_payload( - proplists:get_value(tls_module, Props, ssl), PlusVariant, Conn), + ChannelBinding = scram_sha_auth_payload(PlusVariant, Conn), {ok, ClientState1} = fast_scram:mech_new( #{entity => client, username => Username, hash_method => HashMethod, nonce_size => 16, channel_binding => ChannelBinding, auth_data => #{password => Password}}), @@ -220,14 +219,8 @@ md5_digest_response(ChallengeData, Props) -> {<<"authzid">>, FullJid} ])). -scram_sha_auth_payload(ssl, _, _) -> - {undefined, <<>>}; -scram_sha_auth_payload(fast_tls, none, _) -> - {none, <<>>}; -scram_sha_auth_payload(fast_tls, tls_unique, Conn) -> - {ok, FinishedTLS} = escalus_connection:get_tls_last_message(Conn), - {<<"tls-unique">>, FinishedTLS}. - +scram_sha_auth_payload(_, _) -> + {none, <<>>}. hex_md5(Data) -> binary:encode_hex(crypto:hash(md5, Data), lowercase). diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl index 052077e..df09b18 100644 --- a/src/escalus_tcp.erl +++ b/src/escalus_tcp.erl @@ -9,8 +9,6 @@ -behaviour(escalus_connection). -include_lib("exml/include/exml_stream.hrl"). --include_lib("exml/include/exml.hrl"). --include("escalus.hrl"). %% Escalus transport callbacks -export([connect/1, @@ -64,7 +62,6 @@ host => binary() | inet:ip_address() | inet:hostname(), port => pos_integer(), ssl => boolean(), - tls_module => ssl | fast_tls, stream_management => boolean(), manual_ack => boolean(), iface => inet:ip_address(), @@ -200,7 +197,6 @@ set_active(Pid, Active) -> -spec init({opts(), pid()}) -> {ok, state()}. init({Opts, Owner}) -> #{ssl := IsSSLConnection, - tls_module := TLSMod, on_reply := OnReplyFun, on_request := OnRequestFun, parser_opts := ParserOpts, @@ -213,7 +209,6 @@ init({Opts, Owner}) -> socket = Socket, parser = Parser, ssl = IsSSLConnection, - tls_module = TLSMod, sm_state = SM, event_client = EventClient, on_reply = OnReplyFun, @@ -226,13 +221,11 @@ handle_call(get_sm_h, _From, #state{sm_state = {_, H, _}} = State) -> handle_call({set_sm_h, H}, _From, #state{sm_state = {A, _OldH, S}} = State) -> NewState = State#state{sm_state={A, H, S}}, {reply, {ok, H}, NewState}; -handle_call({upgrade_to_tls, SSLOpts}, _From, #state{socket = Socket, - tls_module = TLSMod} = State) -> - case tcp_to_tls(TLSMod, Socket, SSLOpts) of +handle_call({upgrade_to_tls, SSLOpts}, _From, #state{socket = Socket} = State) -> + case ssl:connect(Socket, SSLOpts) of {ok, TlsSocket} -> {ok, Parser} = exml_stream:new_parser(), - {reply, TlsSocket, State#state{socket = TlsSocket, parser = Parser, - ssl = true, tls_module = TLSMod}}; + {reply, TlsSocket, State#state{socket = TlsSocket, parser = Parser, ssl = true}}; {error, _} = E -> {reply, E, State} end; @@ -256,15 +249,11 @@ handle_call({set_active, Active}, _From, State) -> {reply, ok, set_active_opt(State, Active)}; handle_call({set_filter_pred, Pred}, _From, State) -> {reply, ok, State#state{filter_pred = Pred}}; -handle_call(get_tls_last_message, _From, - #state{socket = Socket, ssl = true, tls_module = fast_tls} = S) -> - Reply = fast_tls:get_tls_last_message(self, Socket), - {reply, Reply, S}; handle_call(get_tls_last_message, _From, #state{} = S) -> {reply, {error, undefined_tls_message}, S}; -handle_call(kill_connection, _, #state{socket = Socket, ssl = SSL, tls_module = TLSMod} = S) -> +handle_call(kill_connection, _, #state{socket = Socket, ssl = SSL} = S) -> case SSL of - true -> TLSMod:close(Socket); + true -> ssl:close(Socket); false -> gen_tcp:close(Socket) end, close_compression_streams(S#state.compress), @@ -292,14 +281,9 @@ handle_cast(stop, State) -> handle_info({tcp, Socket, Data}, #state{socket = Socket, ssl = false} = State) -> NewState = handle_data(Socket, Data, State), {noreply, NewState}; -handle_info({ssl, Socket, Data}, #state{socket = Socket, ssl = true, tls_module = ssl} = State) -> +handle_info({ssl, Socket, Data}, #state{socket = Socket, ssl = true} = State) -> NewState = handle_data(Socket, Data, State), {noreply, NewState}; -handle_info({tcp, TcpSocket, Data}, #state{socket = {tlssock, TcpSocket, _} = TlsSocket, - ssl = true, tls_module = fast_tls} = State) -> - {ok, NewData} = fast_tls:recv_data(TlsSocket, Data), - NewState = handle_data(TlsSocket, NewData, State), - {noreply, NewState}; handle_info({tcp_closed, _Socket}, #state{} = State) -> {stop, normal, State}; handle_info({ssl_closed, _Socket}, #state{} = State) -> @@ -312,10 +296,12 @@ handle_info(_, State) -> {noreply, State}. -spec terminate(term(), state()) -> term(). -terminate(Reason, #state{socket = Socket, ssl = true, tls_module = TLSMod} = State) -> - common_terminate(TLSMod, Socket, Reason, State); -terminate(Reason, #state{socket = Socket} = State) -> - common_terminate(gen_tcp, Socket, Reason, State). +terminate(_Reason, #state{socket = Socket, ssl = true, parser = Parser}) -> + exml_stream:free_parser(Parser), + ssl:close(Socket); +terminate(_Reason, #state{socket = Socket, parser = Parser}) -> + exml_stream:free_parser(Parser), + gen_tcp:close(Socket). -spec code_change(term(), state(), term()) -> {ok, state()}. code_change(_OldVsn, State, _Extra) -> @@ -330,7 +316,6 @@ default_options() -> #{host => <<"localhost">>, port => 5222, ssl => false, - tls_module => ssl, stream_management => false, manual_ack => false, on_reply => fun(_) -> ok end, @@ -355,26 +340,25 @@ default_socket_options() -> %%% Helpers %%%=================================================================== set_active_opt( - #state{ssl = SSL, socket = Soc, tls_module = TLSMod} = State, Act) when is_boolean(Act) -> - set_active_opt(SSL, TLSMod, Soc, Act), + #state{ssl = SSL, socket = Soc} = State, Act) when is_boolean(Act) -> + set_active_opt(SSL, Soc, Act), State#state{active = Act}; set_active_opt( - #state{ssl = SSL, socket = Soc, active = Act, tls_module = TLSMod} = State, current_opt) -> - set_active_opt(SSL, TLSMod, Soc, Act), + #state{ssl = SSL, socket = Soc, active = Act} = State, current_opt) -> + set_active_opt(SSL, Soc, Act), State; -set_active_opt(#state{ssl = SSL, socket = Soc, tls_module = TLSMod} = State, once) -> - set_active_opt(SSL, TLSMod, Soc, true), +set_active_opt(#state{ssl = SSL, socket = Soc} = State, once) -> + set_active_opt(SSL, Soc, true), State#state{active = false}; -set_active_opt(#state{ssl = SSL, socket = Soc, tls_module = TLSMod} = State, at_least_once) -> - set_active_opt(SSL, TLSMod, Soc, true), +set_active_opt(#state{ssl = SSL, socket = Soc} = State, at_least_once) -> + set_active_opt(SSL, Soc, true), State. - -set_active_opt(true, TLSMod, Socket, true) -> - TLSMod:setopts(Socket, [{active, once}]); -set_active_opt(false, _, Socket, true) -> +set_active_opt(true, Socket, true) -> + ssl:setopts(Socket, [{active, once}]); +set_active_opt(false, Socket, true) -> inet:setopts(Socket, [{active, once}]); -set_active_opt(_,_,_,_) -> +set_active_opt(_, _, _) -> ok. handle_data(Socket, Data, #state{parser = Parser, @@ -434,15 +418,11 @@ maybe_compress_and_send(Data, #state{ compress = {zlib, {_, Zout}} } = State) -> maybe_compress_and_send(Data, State) -> raw_send(Data, State). -raw_send(Data, #state{socket = Socket, ssl = true, tls_module = TLSMod}) -> - TLSMod:send(Socket, Data); +raw_send(Data, #state{socket = Socket, ssl = true}) -> + ssl:send(Socket, Data); raw_send(Data, #state{socket = Socket}) -> gen_tcp:send(Socket, Data). -common_terminate(SocketModule, Socket, _Reason, #state{parser = Parser}) -> - exml_stream:free_parser(Parser), - SocketModule:close(Socket). - wait_until_closed(Socket) -> receive {tcp_closed, Socket} -> @@ -479,7 +459,6 @@ close_compression_streams({zlib, {Zin, Zout}}) -> end. do_connect(#{ssl := IsSSLConn, - tls_module := TLSMod, on_connect := OnConnectFun, host := Host, port := Port, @@ -488,7 +467,7 @@ do_connect(#{ssl := IsSSLConn, Address = host_to_inet(Host), SocketOpts = get_socket_opts(Opts), TimeB = erlang:system_time(microsecond), - Reply = maybe_ssl_connection(IsSSLConn, TLSMod, Address, Port, SocketOpts, SSLOpts, HibernateAfter), + Reply = maybe_ssl_connection(IsSSLConn, Address, Port, SocketOpts, SSLOpts, HibernateAfter), TimeA = erlang:system_time(microsecond), ConnectionTime = TimeA - TimeB, case Reply of @@ -499,30 +478,11 @@ do_connect(#{ssl := IsSSLConn, end, Reply. -maybe_ssl_connection(true, fast_tls, Address, Port, SocketOpts, SSLOpts, _) -> - {ok, GenTcpSocket} = gen_tcp:connect(Address, Port, SocketOpts), - tcp_to_tls(fast_tls, GenTcpSocket, SSLOpts); -maybe_ssl_connection(true, ssl, Address, Port, SocketOpts, SSLOpts, HibernateAfter) -> +maybe_ssl_connection(true, Address, Port, SocketOpts, SSLOpts, HibernateAfter) -> ssl:connect(Address, Port, SocketOpts ++ SSLOpts ++ [{hibernate_after, HibernateAfter}]); -maybe_ssl_connection(_, _, Address, Port, SocketOpts, _, _) -> +maybe_ssl_connection(_, Address, Port, SocketOpts, _, _) -> gen_tcp:connect(Address, Port, SocketOpts). -tcp_to_tls(fast_tls, GenTcpSocket, SSLOpts) -> - inet:setopts(GenTcpSocket, [{active, false}]), - case fast_tls:tcp_to_tls(GenTcpSocket, [connect | SSLOpts]) of - {ok, TlsSocket} -> - %% fast_tls requires dummy recv_data/2 call to accomplish TLS handshake - fast_tls:recv(TlsSocket, 0, 100), - fast_tls:recv_data(TlsSocket, <<>>), - fast_tls:recv_data(TlsSocket, <<>>), - fast_tls:setopts(TlsSocket, [{active, once}]), - {ok, TlsSocket}; - {error, _} = E -> - E - end; -tcp_to_tls(ssl, GenTcpSocket, SSLOpts) -> - ssl:connect(GenTcpSocket, SSLOpts). - %%=================================================================== %%% Init options parsing helpers %%%=================================================================== diff --git a/src/escalus_tcp.hrl b/src/escalus_tcp.hrl index 9347549..a030079 100644 --- a/src/escalus_tcp.hrl +++ b/src/escalus_tcp.hrl @@ -15,11 +15,10 @@ %%============================================================================== -record(state, {owner, - socket :: gen_tcp:socket() | ssl:sslsocket() | fast_tls:tls_socket(), + socket :: gen_tcp:socket() | ssl:sslsocket(), parser, filter_pred, ssl = false, - tls_module = ssl, compress = false, event_client, on_reply, From 4608a427d86f34073a080f67dfc3d6f5353337d5 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:29:59 +0100 Subject: [PATCH 20/23] Say the client does not support channel binding when not requested --- src/escalus_auth.erl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index a600337..de99b62 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -30,7 +30,7 @@ -type client() :: escalus_connection:client(). -type user_spec() :: escalus_users:user_spec(). -type hash_type() :: fast_scram:sha_type(). --type plus_variant() :: none | tls_unique. +-type plus_variant() :: undefined | none | tls_exporter. -type scram_options() :: #{plus_variant := plus_variant(), hash_type := hash_type(), xmpp_method := binary() @@ -66,27 +66,27 @@ auth_digest_md5(Conn, Props) -> %% SCRAM Regular -spec auth_sasl_scram_sha1(client(), user_spec()) -> ok. auth_sasl_scram_sha1(Conn, Props) -> - Options = #{plus_variant => none, hash_type => sha, xmpp_method => <<"SCRAM-SHA-1">>}, + Options = #{plus_variant => undefined, hash_type => sha, xmpp_method => <<"SCRAM-SHA-1">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha224(client(), user_spec()) -> ok. auth_sasl_scram_sha224(Conn, Props) -> - Options = #{plus_variant => none, hash_type => sha224, xmpp_method => <<"SCRAM-SHA-224">>}, + Options = #{plus_variant => undefined, hash_type => sha224, xmpp_method => <<"SCRAM-SHA-224">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha256(client(), user_spec()) -> ok. auth_sasl_scram_sha256(Conn, Props) -> - Options = #{plus_variant => none, hash_type => sha256, xmpp_method => <<"SCRAM-SHA-256">>}, + Options = #{plus_variant => undefined, hash_type => sha256, xmpp_method => <<"SCRAM-SHA-256">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha384(client(), user_spec()) -> ok. auth_sasl_scram_sha384(Conn, Props) -> - Options = #{plus_variant => none, hash_type => sha384, xmpp_method => <<"SCRAM-SHA-384">>}, + Options = #{plus_variant => undefined, hash_type => sha384, xmpp_method => <<"SCRAM-SHA-384">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha512(client(), user_spec()) -> ok. auth_sasl_scram_sha512(Conn, Props) -> - Options = #{plus_variant => none, hash_type => sha512, xmpp_method => <<"SCRAM-SHA-512">>}, + Options = #{plus_variant => undefined, hash_type => sha512, xmpp_method => <<"SCRAM-SHA-512">>}, auth_sasl_scram(Options, Conn, Props). %% SCRAM PLUS @@ -219,7 +219,9 @@ md5_digest_response(ChallengeData, Props) -> {<<"authzid">>, FullJid} ])). -scram_sha_auth_payload(_, _) -> +scram_sha_auth_payload(undefined, _) -> + {undefined, <<>>}; +scram_sha_auth_payload(none, _) -> {none, <<>>}. hex_md5(Data) -> From b329a2789e19f8cb37063e9ef9fc39fbcbbb8c47 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:33:15 +0100 Subject: [PATCH 21/23] Implement TLSv1.3 tls-exporter channel binding --- src/escalus_auth.erl | 18 ++++++++++++------ src/escalus_connection.erl | 30 ++++++++++++++++++++++++------ src/escalus_tcp.erl | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index de99b62..4189392 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -38,6 +38,8 @@ -include_lib("exml/include/exml.hrl"). +-define(CB_LABEL, <<"EXPORTER-Channel-Binding">>). + %%-------------------------------------------------------------------- %% Public API %%-------------------------------------------------------------------- @@ -92,31 +94,31 @@ auth_sasl_scram_sha512(Conn, Props) -> %% SCRAM PLUS -spec auth_sasl_scram_sha1_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha1_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha, + Options = #{plus_variant => tls_exporter, hash_type => sha, xmpp_method => <<"SCRAM-SHA-1-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha224_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha224_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha224, + Options = #{plus_variant => tls_exporter, hash_type => sha224, xmpp_method => <<"SCRAM-SHA-224-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha256_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha256_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha256, + Options = #{plus_variant => tls_exporter, hash_type => sha256, xmpp_method => <<"SCRAM-SHA-256-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha384_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha384_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha384, + Options = #{plus_variant => tls_exporter, hash_type => sha384, xmpp_method => <<"SCRAM-SHA-384-PLUS">>}, auth_sasl_scram(Options, Conn, Props). -spec auth_sasl_scram_sha512_plus(client(), user_spec()) -> ok. auth_sasl_scram_sha512_plus(Conn, Props) -> - Options = #{plus_variant => tls_unique, hash_type => sha512, + Options = #{plus_variant => tls_exporter, hash_type => sha512, xmpp_method => <<"SCRAM-SHA-512-PLUS">>}, auth_sasl_scram(Options, Conn, Props). @@ -222,7 +224,11 @@ md5_digest_response(ChallengeData, Props) -> scram_sha_auth_payload(undefined, _) -> {undefined, <<>>}; scram_sha_auth_payload(none, _) -> - {none, <<>>}. + {none, <<>>}; +scram_sha_auth_payload(tls_exporter, Conn) -> + {ok, [Material | _]} = escalus_connection:export_key_materials( + Conn, [?CB_LABEL], [no_context], [32], true), + {<<"tls-exporter">>, Material}. hex_md5(Data) -> binary:encode_hex(crypto:hash(md5, Data), lowercase). diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index 474922d..a17a76d 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -31,7 +31,7 @@ get_sm_h/1, set_sm_h/2, set_filter_predicate/2, - get_tls_last_message/1, + export_key_materials/5, reset_parser/1, is_connected/1, wait_for_close/1, @@ -87,6 +87,16 @@ -callback set_filter_predicate(pid(), filter_pred()) -> ok. -callback stop(pid()) -> ok | already_stopped. -callback kill(pid()) -> ok | already_stopped. +-callback export_key_materials(pid(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +-optional_callbacks([export_key_materials/5]). -callback stream_start_req(user_spec()) -> exml_stream:element(). -callback stream_end_req(user_spec()) -> exml_stream:element(). @@ -390,11 +400,19 @@ set_sm_h(#client{module = Mod}, _) -> set_filter_predicate(#client{module = Module, rcv_pid = Pid}, Pred) -> Module:set_filter_predicate(Pid, Pred). --spec get_tls_last_message(client()) -> {ok, binary()} | {error, undefined_tls_message}. -get_tls_last_message(#client{module = escalus_tcp, rcv_pid = Pid}) -> - escalus_tcp:get_tls_last_message(Pid); -get_tls_last_message(#client{module = Mod}) -> - error({get_tls_last_message, {undefined_for_escalus_module, Mod}}). +-spec export_key_materials(client(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +export_key_materials(#client{module = escalus_tcp, rcv_pid = Pid}, Labels, Contexts, WantedLengths, ConsumeSecret) -> + escalus_tcp:export_key_materials(Pid, Labels, Contexts, WantedLengths, ConsumeSecret); +export_key_materials(#client{module = Mod}, _Labels, _Contexts, _WantedLengths, _ConsumeSecret) -> + error({export_key_materials, {undefined_for_escalus_module, Mod}}). -spec reset_parser(client()) -> ok. reset_parser(#client{module = Mod, rcv_pid = Pid}) -> diff --git a/src/escalus_tcp.erl b/src/escalus_tcp.erl index df09b18..a2e0d4c 100644 --- a/src/escalus_tcp.erl +++ b/src/escalus_tcp.erl @@ -24,7 +24,7 @@ set_sm_h/2, is_using_compression/1, is_using_ssl/1, - get_tls_last_message/1 + export_key_materials/5 ]). %% Connection stream start and end callbacks -export([stream_start_req/1, @@ -119,9 +119,17 @@ is_using_ssl(Pid) -> set_filter_predicate(Pid, Pred) -> gen_server:call(Pid, {set_filter_pred, Pred}). --spec get_tls_last_message(pid()) -> {ok, binary()} | {error, undefined_tls_message}. -get_tls_last_message(Pid) -> - gen_server:call(Pid, get_tls_last_message). +-spec export_key_materials(pid(), Labels, Contexts, WantedLengths, ConsumeSecret) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +export_key_materials(Pid, Labels, Contexts, WantedLengths, ConsumeSecret) -> + gen_server:call(Pid, {export_key_materials, {Labels, Contexts, WantedLengths, ConsumeSecret}}). -spec stop(pid()) -> ok | already_stopped. stop(Pid) -> @@ -249,8 +257,8 @@ handle_call({set_active, Active}, _From, State) -> {reply, ok, set_active_opt(State, Active)}; handle_call({set_filter_pred, Pred}, _From, State) -> {reply, ok, State#state{filter_pred = Pred}}; -handle_call(get_tls_last_message, _From, #state{} = S) -> - {reply, {error, undefined_tls_message}, S}; +handle_call({export_key_materials, Data}, _From, #state{socket = Socket, ssl = true} = S) -> + {reply, do_export_key_materials(Socket, Data), S}; handle_call(kill_connection, _, #state{socket = Socket, ssl = SSL} = S) -> case SSL of true -> ssl:close(Socket); @@ -522,3 +530,20 @@ get_socket_opts(#{socket_opts := SocketOpts}) -> -spec opts_to_map([proplists:property()] | opts()) -> opts(). opts_to_map(Opts) when is_map(Opts) -> Opts; opts_to_map(Opts) when is_list(Opts) -> maps:from_list(Opts). + +-spec do_export_key_materials(ssl:sslsocket(), {Labels, Contexts, WantedLengths, ConsumeSecret}) -> + {ok, ExportKeyMaterials} | + {error, undefined_tls_material | exporter_master_secret_already_consumed | bad_input} + when + Labels :: [binary()], + Contexts :: [binary() | no_context], + WantedLengths :: [non_neg_integer()], + ConsumeSecret :: boolean(), + ExportKeyMaterials :: binary() | [binary()]. +-if(?OTP_RELEASE >= 27). +do_export_key_materials(SslSocket, {Labels, Contexts, WantedLengths, ConsumeSecret}) -> + ssl:export_key_materials(SslSocket, Labels, Contexts, WantedLengths, ConsumeSecret). +-else. +do_export_key_materials(_SslSocket, {_, _, _, _}) -> + {error, undefined_tls_material}. +-endif. From 278e462fcf5e359487205388f53b5b68a62bf41d Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 12:47:18 +0100 Subject: [PATCH 22/23] Expose manual helper to write scram parameters --- src/escalus_auth.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/escalus_auth.erl b/src/escalus_auth.erl index 4189392..bb930a5 100644 --- a/src/escalus_auth.erl +++ b/src/escalus_auth.erl @@ -23,7 +23,8 @@ auth_sasl_oauth/2]). %% Useful helpers for writing own mechanisms --export([get_challenge/2, +-export([auth_sasl_scram/3, + get_challenge/2, wait_for_success/2]). %% Some shorthands From 805bd52a449d06fdbf05e6b063b806947c25870e Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Jan 2025 13:29:50 +0100 Subject: [PATCH 23/23] Make auth_method a more flexible function object --- src/escalus_session.erl | 13 ++++++++----- src/escalus_users.erl | 38 +++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/escalus_session.erl b/src/escalus_session.erl index 09a6728..c4200ee 100644 --- a/src/escalus_session.erl +++ b/src/escalus_session.erl @@ -78,16 +78,19 @@ authenticate(Client = #client{props = Props}) -> %% but as a default we use plain, as it incurrs lower load and better logs (no hashing) %% for common setups. If a different mechanism is required then it should be configured on the %% user specification. - {M, F} = proplists:get_value(auth, Props, {escalus_auth, auth_plain}), - PropsAfterAuth = case apply(M, F, [Client, Props]) of - ok -> Props; - {ok, P} when is_list(P) -> P - end, + PropsAfterAuth = apply_auth_method(Client, Props), escalus_connection:reset_parser(Client), Client1 = escalus_session:start_stream(Client#client{props = PropsAfterAuth}), escalus_session:stream_features(Client1, []), Client1. +apply_auth_method(Client, Props) -> + Fun = proplists:get_value(auth, Props, fun escalus_auth:auth_plain/2), + case apply(Fun, [Client, Props]) of + ok -> Props; + {ok, P} when is_list(P) -> P + end. + -spec bind(client()) -> client(). bind(Client = #client{props = Props0}) -> Resource = proplists:get_value(resource, Props0, ?DEFAULT_RESOURCE), diff --git a/src/escalus_users.erl b/src/escalus_users.erl index 6efa64b..51a6f38 100644 --- a/src/escalus_users.erl +++ b/src/escalus_users.erl @@ -154,46 +154,50 @@ get_server(Config, User) -> get_wspath(Config, User) -> get_user_option(wspath, User, escalus_wspath, Config, undefined). --spec get_auth_method(escalus:config(), user()) -> {module(), atom()}. +-spec get_auth_method(escalus:config(), user()) -> + fun((escalus_connection:client(), escalus_users:user_spec()) -> ok | {ok, escalus_users:user_spec()}). get_auth_method(Config, User) -> AuthMethod = get_user_option(auth_method, User, escalus_auth_method, Config, <<"PLAIN">>), get_auth_method(AuthMethod). --spec get_auth_method(binary() | {module(), atom()}) -> {module(), atom()}. +-spec get_auth_method(binary() | {module(), atom()}) -> + fun((escalus_connection:client(), escalus_users:user_spec()) -> ok | {ok, escalus_users:user_spec()}). get_auth_method(<<"PLAIN">>) -> - {escalus_auth, auth_plain}; + fun escalus_auth:auth_plain/2; get_auth_method(<<"DIGEST-MD5">>) -> - {escalus_auth, auth_digest_md5}; + fun escalus_auth:auth_digest_md5/2; get_auth_method(<<"SASL-ANON">>) -> - {escalus_auth, auth_sasl_anon}; + fun escalus_auth:auth_sasl_anon/2; %% SCRAM Regular get_auth_method(<<"SCRAM-SHA-1">>) -> - {escalus_auth, auth_sasl_scram_sha1}; + fun escalus_auth:auth_sasl_scram_sha1/2; get_auth_method(<<"SCRAM-SHA-224">>) -> - {escalus_auth, auth_sasl_scram_sha224}; + fun escalus_auth:auth_sasl_scram_sha224/2; get_auth_method(<<"SCRAM-SHA-256">>) -> - {escalus_auth, auth_sasl_scram_sha256}; + fun escalus_auth:auth_sasl_scram_sha256/2; get_auth_method(<<"SCRAM-SHA-384">>) -> - {escalus_auth, auth_sasl_scram_sha384}; + fun escalus_auth:auth_sasl_scram_sha384/2; get_auth_method(<<"SCRAM-SHA-512">>) -> - {escalus_auth, auth_sasl_scram_sha512}; + fun escalus_auth:auth_sasl_scram_sha512/2; %% SCRAM PLUS get_auth_method(<<"SCRAM-SHA-1-PLUS">>) -> - {escalus_auth, auth_sasl_scram_sha1_plus}; + fun escalus_auth:auth_sasl_scram_sha1_plus/2; get_auth_method(<<"SCRAM-SHA-224-PLUS">>) -> - {escalus_auth, auth_sasl_scram_sha224_plus}; + fun escalus_auth:auth_sasl_scram_sha224_plus/2; get_auth_method(<<"SCRAM-SHA-256-PLUS">>) -> - {escalus_auth, auth_sasl_scram_sha256_plus}; + fun escalus_auth:auth_sasl_scram_sha256_plus/2; get_auth_method(<<"SCRAM-SHA-384-PLUS">>) -> - {escalus_auth, auth_sasl_scram_sha384_plus}; + fun escalus_auth:auth_sasl_scram_sha384_plus/2; get_auth_method(<<"SCRAM-SHA-512-PLUS">>) -> - {escalus_auth, auth_sasl_scram_sha512_plus}; + fun escalus_auth:auth_sasl_scram_sha512_plus/2; get_auth_method(<<"X-OAUTH">>) -> - {escalus_auth, auth_sasl_oauth}; + fun escalus_auth:auth_sasl_oauth/2; get_auth_method({Mod, Fun}) when is_atom(Mod), is_atom(Fun) -> - {Mod, Fun}. + fun Mod:Fun/2; +get_auth_method(Fun) when is_function(Fun, 2) -> + Fun. -spec get_usp(escalus:config(), user()) -> [binary() | xmpp_domain()]. get_usp(Config, User) ->