From 6abdc5233e4cb285421b5a125da9a244bf6d7d23 Mon Sep 17 00:00:00 2001 From: William Yang Date: Wed, 6 Sep 2023 11:00:07 +0200 Subject: [PATCH 1/9] feat(conn): open conn with registration --- c_src/quicer_connection.c | 63 +++++++++++++++++++++++++++++---------- c_src/quicer_connection.h | 2 +- c_src/quicer_nif.c | 3 +- src/quicer_nif.erl | 8 ++++- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index ca4d05e3..0876b75b 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -93,6 +93,11 @@ static QUIC_STATUS handle_connection_event_resumption_ticket_received( static QUIC_STATUS handle_connection_event_peer_certificate_received( QuicerConnCTX *c_ctx, QUIC_CONNECTION_EVENT *Event); +BOOLEAN +parse_registration(ErlNifEnv *env, + ERL_NIF_TERM options, + QuicerRegistrationCTX **r_ctx); + ERL_NIF_TERM peercert1(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) { @@ -472,20 +477,43 @@ ServerConnectionCallback(HQUIC Connection, return status; } + /* ** Open connection handle only -** No ownership, No monitoring. +** Set ownership but no monitoring. */ ERL_NIF_TERM -open_connection0(ErlNifEnv *env, - __unused_parm__ int argc, - __unused_parm__ const ERL_NIF_TERM argv[]) +open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; ERL_NIF_TERM eHandle; ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_INTERNAL_ERROR); + QuicerRegistrationCTX *r_ctx = NULL; + HQUIC registration = NULL; - assert(argc == 0); + if (argc == 0) + { + registration = GRegistration; + r_ctx = NULL; + } + else + { + assert(argc == 1); + ERL_NIF_TERM options = argv[1]; + if (!parse_registration(env, options, &r_ctx)) + { + return ERROR_TUPLE_2(ATOM_QUIC_REGISTRATION); + } + if (r_ctx) + { + enif_keep_resource(r_ctx); + registration = r_ctx->Registration; + } + else + { + registration = GRegistration; + } + } QuicerConnCTX *c_ctx = init_c_ctx(); if (!c_ctx) @@ -496,29 +524,34 @@ open_connection0(ErlNifEnv *env, c_ctx->owner = AcceptorAlloc(); if (!c_ctx->owner) { - enif_release_resource(c_ctx); - return ERROR_TUPLE_2(ATOM_ERROR_NOT_ENOUGH_MEMORY); + res = ERROR_TUPLE_2(ATOM_ERROR_NOT_ENOUGH_MEMORY); + goto exit; } if (!enif_self(env, &(c_ctx->owner->Pid))) { - enif_release_resource(c_ctx); - return ERROR_TUPLE_2(ATOM_BAD_PID); + res = ERROR_TUPLE_2(ATOM_BAD_PID); + goto exit; } - if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(GRegistration, + if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(registration, ClientConnectionCallback, c_ctx, &(c_ctx->Connection)))) { - destroy_c_ctx(c_ctx); res = ERROR_TUPLE_2(ATOM_STATUS(Status)); + goto exit; } - else + + eHandle = enif_make_resource(env, c_ctx); + return SUCCESS(eHandle); + +exit: + if (r_ctx) { - eHandle = enif_make_resource(env, c_ctx); - res = SUCCESS(eHandle); + enif_release_resource(r_ctx); } + enif_release_resource(c_ctx); return res; } @@ -622,7 +655,7 @@ async_connect3(ErlNifEnv *env, if (!is_reuse_handle) { - if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(GRegistration, + if (QUIC_FAILED(Status = MsQuic->ConnectionOpen(Registration, ClientConnectionCallback, c_ctx, &(c_ctx->Connection)))) diff --git a/c_src/quicer_connection.h b/c_src/quicer_connection.h index e5d43ce6..fdb17131 100644 --- a/c_src/quicer_connection.h +++ b/c_src/quicer_connection.h @@ -26,7 +26,7 @@ typedef enum QUICER_CONNECTION_EVENT_MASKS } QUICER_CONNECTION_EVENT_MASK; ERL_NIF_TERM -open_connection0(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM async_connect3(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM diff --git a/c_src/quicer_nif.c b/c_src/quicer_nif.c index 594b60f8..92dc20d7 100644 --- a/c_src/quicer_nif.c +++ b/c_src/quicer_nif.c @@ -1470,7 +1470,8 @@ static ErlNifFunc nif_funcs[] = { { "start_listener", 3, start_listener3, 0}, { "stop_listener", 1, stop_listener1, 0}, { "close_listener", 1, close_listener1, 0}, - { "open_connection", 0, open_connection0, 0}, + { "open_connection", 0, open_connectionX, 0}, + { "open_connection", 1, open_connectionX, 0}, { "async_connect", 3, async_connect3, 0}, { "async_accept", 2, async_accept2, 0}, { "async_handshake", 1, async_handshake_1, 0}, diff --git a/src/quicer_nif.erl b/src/quicer_nif.erl index be0eace4..fe29b741 100644 --- a/src/quicer_nif.erl +++ b/src/quicer_nif.erl @@ -50,7 +50,9 @@ ]). %% For tests only --export([open_connection/0]). +-export([ open_connection/0 + , open_connection/1 + ]). -on_load(init/0). @@ -154,6 +156,10 @@ stop_listener(_Listener) -> open_connection() -> erlang:nif_error(nif_library_not_loaded). +-spec open_connection(#{ quic_registration => reg_handle()}) -> {ok, connection_handle()} | {error, atom_reason()}. +open_connection(_) -> + erlang:nif_error(nif_library_not_loaded). + -spec async_connect(hostname(), inet:port_number(), conn_opts()) -> {ok, connection_handle()} | {error, conn_open_error | config_error | conn_start_error} | From 562fa62f05bf3ca552d4adb9284b0390754f2692 Mon Sep 17 00:00:00 2001 From: William Yang Date: Wed, 6 Sep 2023 11:14:03 +0200 Subject: [PATCH 2/9] test: new test suite for conn --- test/quicer_connecion_SUITE.erl | 141 ++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 test/quicer_connecion_SUITE.erl diff --git a/test/quicer_connecion_SUITE.erl b/test/quicer_connecion_SUITE.erl new file mode 100644 index 00000000..ba1104a1 --- /dev/null +++ b/test/quicer_connecion_SUITE.erl @@ -0,0 +1,141 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(quicer_connecion_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +%%-------------------------------------------------------------------- +%% @spec suite() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,30}}]. + +%%-------------------------------------------------------------------- +%% @spec init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_suite(Config0) -> term() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_group(GroupName, Config0) -> +%% term() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_testcase(TestCase, Config0) -> +%% term() | {save_config,Config1} | {fail,Reason} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%% @end +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% @spec all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +all() -> + quicer_test_lib:all_tcs(?MODULE). + +%%-------------------------------------------------------------------- +%% @spec TestCase(Config0) -> +%% ok | exit() | {skip,Reason} | {comment,Comment} | +%% {save_config,Config1} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% Comment = term() +%% @end +%%-------------------------------------------------------------------- +tc_open0(_) -> + {ok, H} = quicer_nif:open_connection(), + quicer:close_connection(H). + +tc_open1(_) -> + {ok, H} = quicer_nif:open_connection(#{}), + quicer:close_connection(H). + +tc_open2(Config0) -> + Config = maps:from_list(Config0), + {ok, H} = quicer_nif:open_connection(Config), + quicer:close_connection(H). From 1993b5d74cf4069e5da230263a29f9f235a7af47 Mon Sep 17 00:00:00 2001 From: William Yang Date: Wed, 6 Sep 2023 16:03:27 +0200 Subject: [PATCH 3/9] feat(conn): connection registrations --- c_src/quicer_config.c | 107 +++++++------------------------------- c_src/quicer_config.h | 9 +--- c_src/quicer_connection.c | 60 +++++++++++++++++---- c_src/quicer_connection.h | 2 +- c_src/quicer_ctx.h | 1 + c_src/quicer_listener.c | 3 +- c_src/quicer_nif.h | 1 + c_src/quicer_tls.c | 30 ++++++++--- c_src/quicer_tls.h | 9 ++-- test/quicer_SUITE.erl | 20 ++++--- 10 files changed, 113 insertions(+), 129 deletions(-) diff --git a/c_src/quicer_config.c b/c_src/quicer_config.c index 05e2961e..65c24172 100644 --- a/c_src/quicer_config.c +++ b/c_src/quicer_config.c @@ -17,6 +17,7 @@ limitations under the License. #include "quicer_config.h" #include "quicer_internal.h" #include "quicer_queue.h" +#include "quicer_tls.h" #include extern BOOLEAN isRegistered; @@ -266,14 +267,17 @@ ServerLoadConfiguration(ErlNifEnv *env, ERL_NIF_TERM ClientLoadConfiguration(ErlNifEnv *env, const ERL_NIF_TERM *options, // map - HQUIC *Configuration, - bool HasCaCertFile) + HQUIC Registration, + HQUIC *Configuration) { QUIC_SETTINGS Settings = { 0 }; - char cert_path[PATH_MAX + 1] = { 0 }; - char key_path[PATH_MAX + 1] = { 0 }; - char password[256] = { 0 }; ERL_NIF_TERM ret = ATOM_OK; + + if (!isRegistered && (Registration == GRegistration)) + { + return ATOM_REG_FAILED; + } + // // Configures the client's idle timeout. // @@ -291,59 +295,21 @@ ClientLoadConfiguration(ErlNifEnv *env, /* Settings.IsSet.DesiredVersionsList = TRUE; */ // - // Configures a default client configuration, optionally disabling - // server certificate validation. + // Configures a default client configuration // QUIC_CREDENTIAL_CONFIG CredConfig; CxPlatZeroMemory(&CredConfig, sizeof(CredConfig)); CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE; - CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT; - - if ((get_str_from_map(env, ATOM_CERTFILE, options, cert_path, PATH_MAX + 1) - || get_str_from_map(env, ATOM_CERT, options, cert_path, PATH_MAX + 1)) - && (get_str_from_map(env, ATOM_KEYFILE, options, key_path, PATH_MAX + 1) - || get_str_from_map(env, ATOM_KEY, options, key_path, PATH_MAX + 1))) - { - if (get_str_from_map(env, ATOM_PASSWORD, options, password, 256)) - { - QUIC_CERTIFICATE_FILE_PROTECTED *CertFile - = (QUIC_CERTIFICATE_FILE_PROTECTED *)CXPLAT_ALLOC_NONPAGED( - sizeof(QUIC_CERTIFICATE_FILE_PROTECTED), - QUICER_CERTIFICATE_FILE); - - CertFile->CertificateFile = cert_path; - CertFile->PrivateKeyFile = key_path; - CertFile->PrivateKeyPassword = password; - CredConfig.CertificateFileProtected = CertFile; - CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE_PROTECTED; - } - else - { - QUIC_CERTIFICATE_FILE *CertFile - = (QUIC_CERTIFICATE_FILE *)CXPLAT_ALLOC_NONPAGED( - sizeof(QUIC_CERTIFICATE_FILE), QUICER_CERTIFICATE_FILE); - CertFile->CertificateFile = cert_path; - CertFile->PrivateKeyFile = key_path; - CredConfig.CertificateFile = CertFile; - CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; - } - } + CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_CLIENT; - // Should we try to verify the certificate the server sends? - bool Verify = load_verify(env, options, true); + // certs and keys are optional at client side + parse_cert_options(env, *options, &CredConfig); - if (!Verify) + // If Verify Peer... + if (!parse_verify_options(env, *options, &CredConfig, FALSE)) { - CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; + return ERROR_TUPLE_2(ATOM_VERIFY); } - else if (HasCaCertFile) - { - // Do own validation instead against provided ca certs in cacertfile - CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED; - - CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; - } - // Default to validation against system ca certificates unsigned alpn_buffer_length = 0; QUIC_BUFFER alpn_buffers[MAX_ALPN]; @@ -359,7 +325,7 @@ ClientLoadConfiguration(ErlNifEnv *env, // and settings. // QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(GRegistration, + if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(Registration, alpn_buffers, alpn_buffer_length, &Settings, @@ -386,16 +352,7 @@ ClientLoadConfiguration(ErlNifEnv *env, done: // Cleanup CredConfig - if (QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE == CredConfig.Type) - { - CxPlatFree(CredConfig.CertificateFile, QUICER_CERTIFICATE_FILE); - } - else if (QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE_PROTECTED == CredConfig.Type) - { - CxPlatFree(CredConfig.CertificateFile, - QUICER_CERTIFICATE_FILE_PROTECTED); - } - + free_certificate(&CredConfig); return ret; } @@ -2433,34 +2390,6 @@ set_config_opt(ErlNifEnv *env, return res; } -// @deprecated -int -get_str_from_map(ErlNifEnv *env, - ERL_NIF_TERM key, - const ERL_NIF_TERM *map, - char *buff, - unsigned max_len) -{ - - ERL_NIF_TERM tmp_term; - if (!buff) - { - return 0; - } - unsigned tmp_len = 0; - if (!enif_get_map_value(env, *map, key, &tmp_term)) - { - return 0; - } - - if (!enif_get_list_length(env, tmp_term, &tmp_len) || tmp_len > max_len) - { - return 0; - } - - return enif_get_string(env, tmp_term, buff, tmp_len + 1, ERL_NIF_LATIN1); -} - /* ** Fill str_buffer with string value of key in map. ** In case str_buffer is NULL, then new memory will be allocated, diff --git a/c_src/quicer_config.h b/c_src/quicer_config.h index 8026e1b1..6ef12a77 100644 --- a/c_src/quicer_config.h +++ b/c_src/quicer_config.h @@ -71,8 +71,8 @@ ERL_NIF_TERM ServerLoadConfiguration(ErlNifEnv *env, QUIC_CREDENTIAL_CONFIG *Config); ERL_NIF_TERM ClientLoadConfiguration(ErlNifEnv *env, const ERL_NIF_TERM *option, - HQUIC *Configuration, - bool HasCaCertFile); + HQUIC Registration, + HQUIC *Configuration); bool load_alpn(ErlNifEnv *env, const ERL_NIF_TERM *option, @@ -97,11 +97,6 @@ bool get_uint64_from_map(ErlNifEnv *env, const ERL_NIF_TERM map, ERL_NIF_TERM key, uint64_t *value); -int get_str_from_map(ErlNifEnv *env, - ERL_NIF_TERM key, - const ERL_NIF_TERM *map, - char *buff, - unsigned max_len); char *str_from_map(ErlNifEnv *env, ERL_NIF_TERM key, const ERL_NIF_TERM *map, diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index 0876b75b..288ff070 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------*/ #include "quicer_connection.h" +#include "quicer_config.h" #include "quicer_ctx.h" #include "quicer_tls.h" #include @@ -516,6 +517,8 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } QuicerConnCTX *c_ctx = init_c_ctx(); + c_ctx->r_ctx = r_ctx; + if (!c_ctx) { return ERROR_TUPLE_2(ATOM_ERROR_NOT_ENOUGH_MEMORY); @@ -577,6 +580,7 @@ async_connect3(ErlNifEnv *env, int port = 0; char host[256] = { 0 }; + HQUIC Registration = NULL; if (!enif_get_int(env, eport, &port) && port > 0) { return ERROR_TUPLE_2(ATOM_BADARG); @@ -596,6 +600,15 @@ async_connect3(ErlNifEnv *env, assert(c_ctx->is_closed); assert(c_ctx->owner); is_reuse_handle = TRUE; + if (c_ctx->r_ctx && c_ctx->r_ctx->Registration) + { + Registration = c_ctx->r_ctx->Registration; + } + else + { + Registration = GRegistration; + } + // @TODO we should take lock here } else { @@ -606,12 +619,34 @@ async_connect3(ErlNifEnv *env, { assert(!c_ctx); c_ctx = init_c_ctx(); + + // Get Reg for c_ctx, quic_registration is optional + if (!parse_registration(env, eoptions, &c_ctx->r_ctx)) + { + res = ERROR_TUPLE_2(ATOM_QUIC_REGISTRATION); + goto Error; + } + + if (c_ctx->r_ctx && c_ctx->r_ctx->Registration) + { + // quic_registration is set + enif_keep_resource(c_ctx->r_ctx); + Registration = c_ctx->r_ctx->Registration; + } + else + { + // quic_registration is not set, use global registration + // msquic should reject if global registration is NULL (closed) + Registration = GRegistration; + } + if ((c_ctx->owner = AcceptorAlloc()) == NULL) { res = ERROR_TUPLE_2(ATOM_ERROR_NOT_ENOUGH_MEMORY); goto Error; } + // set owner if (!enif_self(env, &(c_ctx->owner->Pid))) { res = ERROR_TUPLE_2(ATOM_BAD_PID); @@ -627,25 +662,30 @@ async_connect3(ErlNifEnv *env, goto Error; } - ERL_NIF_TERM ecacertfile; + // parse opt cacertfile + char *cacertfile = NULL; + if (!parse_cacertfile_option(env, eoptions, &cacertfile)) + { + // TLS opt error not file content error + res = ERROR_TUPLE_2(ATOM_CACERTFILE); + goto Error; + } - if (enif_get_map_value(env, eoptions, ATOM_CACERTFILE, &ecacertfile)) + if (cacertfile) { - char cacertfile[PATH_MAX]; - if (!(enif_get_string( - env, ecacertfile, cacertfile, PATH_MAX, ERL_NIF_LATIN1) - > 0 - && build_trustedstore(cacertfile, &c_ctx->trusted))) + if (!build_trustedstore(cacertfile, &c_ctx->trusted)) { - res = ERROR_TUPLE_2(ATOM_BADARG); + free(cacertfile); + res = ERROR_TUPLE_2(ATOM_CERT_ERROR); goto Error; } + free(cacertfile); } // convert eoptions to Configuration - bool HasCaCertfile = c_ctx->trusted != NULL; + ERL_NIF_TERM estatus = ClientLoadConfiguration( - env, &eoptions, &(c_ctx->config_resource->Configuration), HasCaCertfile); + env, &eoptions, Registration, &(c_ctx->config_resource->Configuration)); if (!IS_SAME_TERM(ATOM_OK, estatus)) { diff --git a/c_src/quicer_connection.h b/c_src/quicer_connection.h index fdb17131..b24ae0dc 100644 --- a/c_src/quicer_connection.h +++ b/c_src/quicer_connection.h @@ -16,8 +16,8 @@ limitations under the License. #ifndef __QUICER_CONNECTION_H_ #define __QUICER_CONNECTION_H_ +#include "erl_nif.h" #include "quicer_internal.h" -#include "quicer_nif.h" #include typedef enum QUICER_CONNECTION_EVENT_MASKS diff --git a/c_src/quicer_ctx.h b/c_src/quicer_ctx.h index c3c472c9..531d2fcc 100644 --- a/c_src/quicer_ctx.h +++ b/c_src/quicer_ctx.h @@ -77,6 +77,7 @@ typedef struct QuicerConnCTX // for server, inherit from l_ctx // for client, alloc on its own QuicerConfigCTX *config_resource; + QuicerRegistrationCTX *r_ctx; HQUIC Connection; QUICER_ACCEPTOR_QUEUE *acceptor_queue; ACCEPTOR *owner; diff --git a/c_src/quicer_listener.c b/c_src/quicer_listener.c index 25a434f5..0443031f 100644 --- a/c_src/quicer_listener.c +++ b/c_src/quicer_listener.c @@ -256,6 +256,7 @@ listen2(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) // Start build CredConfig from with listen opts QUIC_CREDENTIAL_CONFIG CredConfig; + CxPlatZeroMemory(&CredConfig, sizeof(CredConfig)); CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE; @@ -264,7 +265,7 @@ listen2(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) return ERROR_TUPLE_2(ATOM_QUIC_TLS); } - if (!parse_verify_options_server(env, options, &CredConfig)) + if (!parse_verify_options(env, options, &CredConfig, TRUE)) { return ERROR_TUPLE_2(ATOM_VERIFY); } diff --git a/c_src/quicer_nif.h b/c_src/quicer_nif.h index c2e96a39..cf2b1686 100644 --- a/c_src/quicer_nif.h +++ b/c_src/quicer_nif.h @@ -33,6 +33,7 @@ limitations under the License. #include "quicer_queue.h" #include "quicer_reg.h" #include "quicer_stream.h" +#include "quicer_tls.h" #include "quicer_tp.h" // @todo is 16 enough? diff --git a/c_src/quicer_tls.c b/c_src/quicer_tls.c index 3e4d3212..974724fd 100644 --- a/c_src/quicer_tls.c +++ b/c_src/quicer_tls.c @@ -32,7 +32,6 @@ parse_cert_options(ErlNifEnv *env, { return FALSE; } - CxPlatZeroMemory(CredConfig, sizeof(QUIC_CREDENTIAL_CONFIG)); if (!(certfile = str_from_map(env, ATOM_CERTFILE, &options, NULL, PATH_MAX + 1))) @@ -91,9 +90,10 @@ parse_cert_options(ErlNifEnv *env, * verify : boolean() | undefined */ BOOLEAN -parse_verify_options_server(ErlNifEnv *env, - ERL_NIF_TERM options, - QUIC_CREDENTIAL_CONFIG *CredConfig) +parse_verify_options(ErlNifEnv *env, + ERL_NIF_TERM options, + QUIC_CREDENTIAL_CONFIG *CredConfig, + BOOLEAN is_server) { BOOLEAN verify = load_verify(env, &options, FALSE); @@ -104,8 +104,26 @@ parse_verify_options_server(ErlNifEnv *env, } else { - // Server flag: - CredConfig->Flags |= QUIC_CREDENTIAL_FLAG_REQUIRE_CLIENT_AUTHENTICATION; + // Verify peer is enabled + if (is_server) + { + CredConfig->Flags + |= QUIC_CREDENTIAL_FLAG_REQUIRE_CLIENT_AUTHENTICATION; + } + else + { + ERL_NIF_TERM tmp; + if (enif_get_map_value(env, options, ATOM_CACERTFILE, &tmp)) + { + // cacertfile is set, use it for self validation. + CredConfig->Flags + |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; + CredConfig->Flags + |= QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED; + } + CredConfig->Flags + |= QUIC_CREDENTIAL_FLAG_USE_TLS_BUILTIN_CERTIFICATE_VALIDATION; + } } return TRUE; } diff --git a/c_src/quicer_tls.h b/c_src/quicer_tls.h index ec0553fd..9e09540e 100644 --- a/c_src/quicer_tls.h +++ b/c_src/quicer_tls.h @@ -15,7 +15,7 @@ limitations under the License. -------------------------------------------------------------------*/ #ifndef QUICER_TLS_H_ #define QUICER_TLS_H_ - +#include "msquic.h" #include "quicer_nif.h" BOOLEAN parse_cert_options(ErlNifEnv *env, @@ -23,9 +23,10 @@ BOOLEAN parse_cert_options(ErlNifEnv *env, QUIC_CREDENTIAL_CONFIG *CredConfig); BOOLEAN -parse_verify_options_server(ErlNifEnv *env, - ERL_NIF_TERM options, - QUIC_CREDENTIAL_CONFIG *CredConfig); +parse_verify_options(ErlNifEnv *env, + ERL_NIF_TERM options, + QUIC_CREDENTIAL_CONFIG *CredConfig, + BOOLEAN is_server); BOOLEAN parse_cacertfile_option(ErlNifEnv *env, diff --git a/test/quicer_SUITE.erl b/test/quicer_SUITE.erl index c9c2e02e..11aadbef 100644 --- a/test/quicer_SUITE.erl +++ b/test/quicer_SUITE.erl @@ -1827,11 +1827,11 @@ tc_setopt_conn_remote_addr(_Config) -> ok = quicer:setopt(Conn, param_conn_remote_address, "8.8.8.8:443"), ok = quicer:setopt(Conn, param_conn_datagram_receive_enabled, false), Res = quicer:connect("google.com", 443, [ {verify, verify_peer} - , {handle, Conn} - , {peer_unidi_stream_count, 3} - , {idle_timeout_ms, 5000} - , {handshake_idle_timeout_ms, 5000} - , {alpn, ["h3"]}], 1000), + , {handle, Conn} + , {peer_unidi_stream_count, 3} + , {idle_timeout_ms, 5000} + , {handshake_idle_timeout_ms, 5000} + , {alpn, ["h3"]}], 1000), case Res of {ok, _} -> %% Linux ok; @@ -3320,8 +3320,6 @@ ensure_server_exit_normal(MonRef, Timeout) -> ct:fail("server still running", []) end. - - default_conn_opts_verify(Config, Ca) -> DataDir = ?config(data_dir, Config), [{verify, peer}, @@ -3330,14 +3328,14 @@ default_conn_opts_verify(Config, Ca) -> default_conn_opts_client_cert(Config, Ca) -> DataDir = ?config(data_dir, Config), - [{key, filename:join(DataDir, "client.key")}, - {cert, filename:join(DataDir, "client.pem")}| + [{keyfile, filename:join(DataDir, "client.key")}, + {certfile, filename:join(DataDir, "client.pem")}| default_conn_opts_verify(Config, Ca)]. default_conn_opts_bad_client_cert(Config, Ca) -> DataDir = ?config(data_dir, Config), - [{key, filename:join(DataDir, "other-client.key")}, - {cert, filename:join(DataDir, "other-client.pem")}| + [{keyfile, filename:join(DataDir, "other-client.key")}, + {certfile, filename:join(DataDir, "other-client.pem")}| default_conn_opts_verify(Config, Ca)]. default_listen_opts_client_cert(Config) -> From 8584a6bf3154d63c00a7dbda26c3752a10b43026 Mon Sep 17 00:00:00 2001 From: William Yang Date: Wed, 6 Sep 2023 17:08:44 +0200 Subject: [PATCH 4/9] test: move conn test to quicer_connecion_SUITE --- test/quicer_SUITE.erl | 547 -------------------- test/quicer_connecion_SUITE.erl | 141 ----- test/quicer_connection_SUITE.erl | 859 +++++++++++++++++++++++++++++++ test/quicer_test_lib.erl | 28 +- 4 files changed, 886 insertions(+), 689 deletions(-) delete mode 100644 test/quicer_connecion_SUITE.erl create mode 100644 test/quicer_connection_SUITE.erl diff --git a/test/quicer_SUITE.erl b/test/quicer_SUITE.erl index 11aadbef..fd8ea6e8 100644 --- a/test/quicer_SUITE.erl +++ b/test/quicer_SUITE.erl @@ -44,29 +44,6 @@ , tc_lib_registration_neg/1 , tc_open_listener_inval_reg/1 - , tc_conn_basic/1 - , tc_conn_basic_slow_start/1 - , tc_conn_basic_verify_peer/1 - , tc_conn_basic_verify_peer_no_cacert/1 - - , tc_conn_timeout/1 - , tc_async_conn_timeout/1 - , tc_conn_double_close/1 - , tc_conn_other_port/1 - , tc_conn_with_localaddr/1 - , tc_conn_controlling_process/1 - , tc_conn_controlling_process_demon/1 - - , tc_conn_custom_ca/1 - , tc_conn_custom_ca_other/1 - , tc_conn_client_cert/1 - , tc_conn_client_bad_cert/1 - - , tc_conn_opt_ideal_processor/1 - , tc_conn_opt_share_udp_binding/1 - , tc_conn_opt_local_uni_stream_count/1 - , tc_conn_opt_local_bidi_stream_count/1 - , tc_stream_client_init/1 , tc_stream_client_send_binary/1 , tc_stream_client_send_iolist/1 @@ -334,411 +311,6 @@ tc_open_listener_inval_reg(Config) -> quicer:reg_open(), ok. -tc_conn_basic(Config) -> - {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_basic(Config) end), - receive - {'DOWN', Ref, process, Pid, normal} -> - ok; - {'DOWN', Ref, process, Pid, Error} -> - ct:fail({run_error, Error}) - end. - -run_tc_conn_basic(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, {_, _}} = quicer:sockname(Conn), - ct:pal("closing connection : ~p", [Conn]), - ok = quicer:close_connection(Conn), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_basic_slow_start(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_slow_conn_server(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, {_, _}} = quicer:sockname(Conn), - ok = quicer:close_connection(Conn), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_basic_verify_peer(_Config)-> - {ok, Conn} = quicer:connect("google.com", 443, - [ {verify, verify_peer} - %, {sslkeylogfile, "/tmp/SSLKEYLOGFILE"} - , {peer_unidi_stream_count, 3} - , {alpn, ["h3"]}], 5000), - {ok, {_, _}} = quicer:sockname(Conn), - {ok, Info} = quicer:getopt(Conn, param_tls_handshake_info, quic_tls), - ct:pal("Handshake Info with Google: ~p", [Info]), - ok = quicer:close_connection(Conn), - ok. - -tc_conn_basic_verify_peer_no_cacert(Config)-> - %% Verify that the connection handshake should fail if - %% `verif_peer` is set but CA is unknown - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_slow_conn_server(Owner, Config, Port) - end), - receive listener_ready -> ok end, - - %% ErrorCode is different per platform - {error,transport_down, - #{error := _ErrorCode, - status := ErrorStatus}} = - quicer:connect("localhost", Port, - [ {verify, verify_peer} - , {peer_unidi_stream_count, 3} - , {alpn, ["sample"]}], 5000), - - ?assert(ErrorStatus =:= cert_untrusted_root orelse ErrorStatus =:= bad_certificate), - - receive - {quic, closed, _, _} -> - ct:fail("closed should be flushed") - after 500 -> - ok - end, - - SPid ! done, - ensure_server_exit_normal(Ref), - ok. - -tc_conn_timeout(Config)-> - Port = select_port(), - Owner = self(), - TOut = 10, - {SPid, Ref} = spawn_monitor( - fun() -> - simple_slow_conn_server(Owner, Config, Port, TOut*2) - end), - receive - listener_ready -> - {error, transport_down, #{error := 1, status := connection_idle}} - = quicer:connect("localhost", Port, default_conn_opts(), TOut), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_async_conn_timeout(Config)-> - Port = select_port(), - Owner = self(), - %% The value set here might not be the one actual in use - %% because loss detection takes many facts into account - %% A minimal value will be selected rather than this one - %% for more, look for QuicLossDetectionComputeProbeTimeout - Tout = 1000, - {SPid, Ref} = spawn_monitor( - fun() -> - simple_slow_conn_server(Owner, Config, Port, Tout*2) - end), - receive - listener_ready -> - {ok, H} = quicer:async_connect("localhost", Port, [{handshake_idle_timeout_ms, Tout} | - default_conn_opts()]), - receive - {quic, transport_shutdown, H, Reason} -> - %% silent local close - ?assertEqual(#{ error => 1 - , status => connection_idle}, Reason) - after Tout * 10 -> - ct:fail("conn didn't timeout") - end, - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_double_close(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, {_, _}} = quicer:sockname(Conn), - ok = quicer:close_connection(Conn), - SPid ! done, - quicer:async_close_connection(Conn), - %% Wait for it crash if it will - timer:sleep(1000), - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_other_port(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - ok = quicer:close_connection(Conn), - SPid ! done, - ok = ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_with_localaddr(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), - - {ok, CPort0} = gen_udp:open(0, [{ip, {127, 0, 0, 1}}]), - {ok, {{127, 0, 0, 1}, PortX}} = inet:sockname(CPort0), - ok = gen_udp:close(CPort0), - receive - listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, [{param_conn_local_address, "127.0.0.1:" ++ integer_to_list(PortX)} - | default_conn_opts()], 5000), - ?assertEqual({ok, {{127,0,0,1}, PortX}}, quicer:sockname(Conn)), - ok = quicer:close_connection(Conn), - SPid ! done, - ok = ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_custom_ca(Config) -> - {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_custom_ca(Config) end), - receive - {'DOWN', Ref, process, Pid, normal} -> - ok; - {'DOWN', Ref, process, Pid, Error} -> - ct:fail({run_error, Error}) - end. - -run_tc_conn_custom_ca(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts_verify(Config, "ca"), 5000), - {ok, {_, _}} = quicer:sockname(Conn), - ct:pal("closing connection : ~p", [Conn]), - ok = quicer:close_connection(Conn), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_custom_ca_other(Config) -> - {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_custom_ca_other(Config) end), - receive - {'DOWN', Ref, process, Pid, normal} -> - ok; - {'DOWN', Ref, process, Pid, Error} -> - ct:fail({run_error, Error}) - end. - -run_tc_conn_custom_ca_other(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server_close(Owner, Config, Port) - end), - receive - listener_ready -> - {error,transport_down, - #{error := _ErrorCode, - status := bad_certificate}} = - quicer:connect("localhost", Port, - default_conn_opts_verify(Config, "other-ca"), - 5000), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_client_cert(Config) -> - {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_client_cert(Config) end), - receive - {'DOWN', Ref, process, Pid, normal} -> - ok; - {'DOWN', Ref, process, Pid, Error} -> - ct:fail({run_error, Error}) - end. - -run_tc_conn_client_cert(Config)-> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server_client_cert(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, - default_conn_opts_client_cert(Config, "ca"), - 5000), - {ok, {_, _}} = quicer:sockname(Conn), - ct:pal("closing connection : ~p", [Conn]), - ok = quicer:close_connection(Conn), - SPid ! done, - ensure_server_exit_normal(Ref) - after 1000 -> - ct:fail("timeout") - end. - -tc_conn_client_bad_cert(Config) -> - {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_client_bad_cert(Config) end), - receive - {'DOWN', Ref, process, Pid, normal} -> - ok; - {'DOWN', Ref, process, Pid, Error} -> - ct:fail({run_error, Error}) - end. - -run_tc_conn_client_bad_cert(Config)-> - Port = select_port(), - Owner = self(), - {_SPid, Ref} = spawn_monitor( - fun() -> - simple_conn_server_client_bad_cert(Owner, Config, Port) - end), - receive - listener_ready -> - {ok, Conn} = quicer:connect( - "localhost", Port, - default_conn_opts_bad_client_cert(Config, "ca"), - 5000), - case quicer:start_stream(Conn, []) of - {error, stm_open_error, aborted} -> - %% Depending on the timing, connection open could fail already. - ok; - {ok, Stm} -> - case quicer:send(Stm, <<"ping">>) of - {ok, 4} -> ok; - {error, cancelled} -> ok; - {error, stm_send_error, aborted} -> ok - end, - receive - {quic, transport_shutdown, _Ref, - #{error := _ErrorCode, status := bad_certificate}} -> - _ = flush([]) - after - 2000 -> - Other = flush([]), - ct:fail("Unexpected Msg ~p", [Other]) - end, - ensure_server_exit_normal(Ref) - end - after 1000 -> - ct:fail("timeout") - end. - -flush(Acc) -> - receive - Other -> - flush([Other|Acc]) - after - 0 -> - lists:reverse(Acc) - end. - -tc_conn_opt_ideal_processor(Config) -> - Port = select_port(), - Owner = self(), - {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), - {ok, Stm} = quicer:start_stream(Conn, []), - {ok, 4} = quicer:send(Stm, <<"ping">>), - {ok, Processor} = quicer:getopt(Conn, param_conn_ideal_processor), - ?assert(is_integer(Processor)), - ok = quicer:close_connection(Conn) - after 5000 -> - ct:fail("listener_timeout") - end. - -tc_conn_opt_share_udp_binding(Config) -> - Port = select_port(), - Owner = self(), - {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), - {ok, Stm} = quicer:start_stream(Conn, []), - {ok, 4} = quicer:send(Stm, <<"ping">>), - {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), - ?assert(is_boolean(IsShared)), - {error, invalid_state} = quicer:setopt(Conn, param_conn_share_udp_binding, not IsShared), - {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), - ok = quicer:close_connection(Conn) - after 5000 -> - ct:fail("listener_timeout") - end. - -tc_conn_opt_local_bidi_stream_count(Config) -> - Port = select_port(), - Owner = self(), - {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), - {ok, Stm} = quicer:start_stream(Conn, []), - {ok, 4} = quicer:send(Stm, <<"ping">>), - {ok, Cnt} = quicer:getopt(Conn, param_conn_local_bidi_stream_count), - ?assert(is_integer(Cnt)), - {error, invalid_parameter} = quicer:setopt(Conn, param_conn_local_bidi_stream_count, Cnt + 2), - ok = quicer:close_connection(Conn) - after 5000 -> - ct:fail("listener_timeout") - end. - -tc_conn_opt_local_uni_stream_count(Config) -> - Port = select_port(), - Owner = self(), - {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), - {ok, Stm} = quicer:start_stream(Conn, []), - {ok, 4} = quicer:send(Stm, <<"ping">>), - {ok, Cnt} = quicer:getopt(Conn, param_conn_local_unidi_stream_count), - ?assert(is_integer(Cnt)), - {error, invalid_parameter} = quicer:setopt(Conn, param_conn_local_unidi_stream_count, Cnt + 2), - ok = quicer:close_connection(Conn) - after 5000 -> - ct:fail("listener_timeout") - end. tc_stream_client_init(Config) -> Port = select_port(), @@ -1246,79 +818,7 @@ dgram_client_recv_loop(Conn, ReceivedOnStream, ReceivedViaDgram) -> ct:fail("Unexpected Msg ~p", [Other]) end. -tc_conn_controlling_process(Config) -> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), - ok = quicer:controlling_process(Conn, self()), - {ok, 11} = quicer:send(Stm, <<"ping_active">>), - {ok, _} = quicer:recv(Stm, 11), - {NewOwner, MonRef} = spawn_monitor( - fun() -> - receive - {quic, closed, Conn, _Flags} -> - ok - end - end), - ok = quicer:controlling_process(Conn, NewOwner), - %% Trigger *async* connection shutdown since I am not the conn owner - quicer:async_shutdown_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0), - receive - {'DOWN', MonRef, process, NewOwner, normal} -> - SPid ! done - end, - ensure_server_exit_normal(Ref) - after 6000 -> - ct:fail("timeout") - end. -%% @doc check old owner is demonitored. -tc_conn_controlling_process_demon(Config) -> - Port = select_port(), - Owner = self(), - {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), - receive - listener_ready -> - Parent = self(), - {OldOwner, MonRef} = spawn_monitor( - fun() -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - Res = quicer:controlling_process(Conn, Parent), - exit({Res, Conn}) - end), - Conn = receive - {'DOWN', MonRef, process, OldOwner, {Res, TheConn}} -> - ct:pal("Old Owner is down, mon res: ~p", [Res]), - TheConn - end, - %% Try set owner back to dead previous owner, should fail - ?assertEqual({error, owner_dead}, quicer:controlling_process(Conn, OldOwner)), - %% rollback to this owner. - - {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), - {ok, 11} = quicer:send(Stm, <<"ping_active">>), - {ok, _} = quicer:recv(Stm, 11), - SPid ! done, - - {NewOwner2, MonRef2} = spawn_monitor(fun() -> - receive stop -> ok end - end), - ok = quicer:controlling_process(Conn, NewOwner2), - NewOwner2 ! stop, - receive - {'DOWN', MonRef2, process, NewOwner2, normal} -> ok - end, - ?assertNotMatch({ok, _}, quicer:send(Stm, <<"ping_active">>)), - quicer:async_shutdown_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0), - SPid ! done, - ensure_server_exit_normal(Ref) - after 6000 -> - ct:fail("timeout") - end. %% @doc test conn and stream share the same owner. tc_conn_and_stream_shared_owner(Config) -> @@ -3191,18 +2691,6 @@ simple_conn_server_loop(L, Conn, Owner) -> quicer:close_listener(L) end. -simple_conn_server_close(Owner, Config, Port) -> - {ok, L} = quicer:listen(Port, default_listen_opts(Config)), - Owner ! listener_ready, - {ok, Conn} = quicer:accept(L, [], 1000), - {error, closed} = quicer:handshake(Conn), - receive - done -> - quicer:close_listener(L), - ok; - {quic, shutdown, Conn} -> - quicer:close_listener(L) - end. simple_conn_server_client_cert(Owner, Config, Port) -> {ok, L} = quicer:listen(Port, default_listen_opts_client_cert(Config)), @@ -3231,40 +2719,6 @@ simple_conn_server_client_cert_loop(L, Conn, Owner) -> quicer:close_listener(L) end. -simple_conn_server_client_bad_cert(Owner, Config, Port) -> - {ok, L} = quicer:listen(Port, default_listen_opts_client_cert(Config)), - Owner ! listener_ready, - {ok, Conn} = quicer:accept(L, [], 1000), - {error, closed} = quicer:handshake(Conn), - quicer:close_listener(L). - -simple_slow_conn_server(Owner, Config, Port) -> - simple_slow_conn_server(Owner, Config, Port, 0). -simple_slow_conn_server(Owner, Config, Port, HandshakeDelay) -> - {ok, L} = quicer:listen(Port, [ {handshake_idle_timeout_ms, HandshakeDelay*2+10} - | default_listen_opts(Config)]), - Owner ! listener_ready, - {ok, Conn} = quicer:accept(L, [], 5000), - ct:pal("~p new conn ~p", [?FUNCTION_NAME, Conn]), - timer:sleep(HandshakeDelay), - ok = quicer:async_handshake(Conn), - ct:pal("~p handshake ~p", [?FUNCTION_NAME, Conn]), - receive - {quic, connected, Conn, _} -> - ct:pal("~p Connected ~p", [?FUNCTION_NAME, Conn]), - ok; - {quic, closed, Conn, _Flags} -> - %% for timeout test - ct:pal("~p conn ~p closed", [?FUNCTION_NAME, Conn]), - ok - end, - %% test what happens if handshake twice - {error, invalid_state} = quicer:handshake(Conn), - receive done -> - quicer:close_listener(L), - ok - end. - conn_server_with(Owner, Port, Opts) -> {ok, L} = quicer:listen(Port, Opts), Owner ! listener_ready, @@ -3368,7 +2822,6 @@ retry_with(Fun, Retry, ErrorInfo) -> retry_with(Fun, Retry - 1, NewErrorInfo) end. - flush_streams_available(Conn) -> receive {quic, streams_available, Conn, diff --git a/test/quicer_connecion_SUITE.erl b/test/quicer_connecion_SUITE.erl deleted file mode 100644 index ba1104a1..00000000 --- a/test/quicer_connecion_SUITE.erl +++ /dev/null @@ -1,141 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- --module(quicer_connecion_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("common_test/include/ct.hrl"). --include_lib("stdlib/include/assert.hrl"). --include_lib("snabbkaffe/include/snabbkaffe.hrl"). - -%%-------------------------------------------------------------------- -%% @spec suite() -> Info -%% Info = [tuple()] -%% @end -%%-------------------------------------------------------------------- -suite() -> - [{timetrap,{seconds,30}}]. - -%%-------------------------------------------------------------------- -%% @spec init_per_suite(Config0) -> -%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} -%% Config0 = Config1 = [tuple()] -%% Reason = term() -%% @end -%%-------------------------------------------------------------------- -init_per_suite(Config) -> - Config. - -%%-------------------------------------------------------------------- -%% @spec end_per_suite(Config0) -> term() | {save_config,Config1} -%% Config0 = Config1 = [tuple()] -%% @end -%%-------------------------------------------------------------------- -end_per_suite(_Config) -> - ok. - -%%-------------------------------------------------------------------- -%% @spec init_per_group(GroupName, Config0) -> -%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} -%% GroupName = atom() -%% Config0 = Config1 = [tuple()] -%% Reason = term() -%% @end -%%-------------------------------------------------------------------- -init_per_group(_GroupName, Config) -> - Config. - -%%-------------------------------------------------------------------- -%% @spec end_per_group(GroupName, Config0) -> -%% term() | {save_config,Config1} -%% GroupName = atom() -%% Config0 = Config1 = [tuple()] -%% @end -%%-------------------------------------------------------------------- -end_per_group(_GroupName, _Config) -> - ok. - -%%-------------------------------------------------------------------- -%% @spec init_per_testcase(TestCase, Config0) -> -%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} -%% TestCase = atom() -%% Config0 = Config1 = [tuple()] -%% Reason = term() -%% @end -%%-------------------------------------------------------------------- -init_per_testcase(_TestCase, Config) -> - Config. - -%%-------------------------------------------------------------------- -%% @spec end_per_testcase(TestCase, Config0) -> -%% term() | {save_config,Config1} | {fail,Reason} -%% TestCase = atom() -%% Config0 = Config1 = [tuple()] -%% Reason = term() -%% @end -%%-------------------------------------------------------------------- -end_per_testcase(_TestCase, _Config) -> - ok. - -%%-------------------------------------------------------------------- -%% @spec groups() -> [Group] -%% Group = {GroupName,Properties,GroupsAndTestCases} -%% GroupName = atom() -%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] -%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] -%% TestCase = atom() -%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} -%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | -%% repeat_until_any_ok | repeat_until_any_fail -%% N = integer() | forever -%% @end -%%-------------------------------------------------------------------- -groups() -> - []. - -%%-------------------------------------------------------------------- -%% @spec all() -> GroupsAndTestCases | {skip,Reason} -%% GroupsAndTestCases = [{group,GroupName} | TestCase] -%% GroupName = atom() -%% TestCase = atom() -%% Reason = term() -%% @end -%%-------------------------------------------------------------------- -all() -> - quicer_test_lib:all_tcs(?MODULE). - -%%-------------------------------------------------------------------- -%% @spec TestCase(Config0) -> -%% ok | exit() | {skip,Reason} | {comment,Comment} | -%% {save_config,Config1} | {skip_and_save,Reason,Config1} -%% Config0 = Config1 = [tuple()] -%% Reason = term() -%% Comment = term() -%% @end -%%-------------------------------------------------------------------- -tc_open0(_) -> - {ok, H} = quicer_nif:open_connection(), - quicer:close_connection(H). - -tc_open1(_) -> - {ok, H} = quicer_nif:open_connection(#{}), - quicer:close_connection(H). - -tc_open2(Config0) -> - Config = maps:from_list(Config0), - {ok, H} = quicer_nif:open_connection(Config), - quicer:close_connection(H). diff --git a/test/quicer_connection_SUITE.erl b/test/quicer_connection_SUITE.erl new file mode 100644 index 00000000..3530e638 --- /dev/null +++ b/test/quicer_connection_SUITE.erl @@ -0,0 +1,859 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(quicer_connection_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include("quicer.hrl"). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-import(quicer_test_lib, [ default_listen_opts/1 + , default_conn_opts/0 + , default_stream_opts/0 + , select_free_port/1 + , flush/1 + , ensure_server_exit_normal/1 + , ensure_server_exit_normal/2 + ]). + +%%-------------------------------------------------------------------- +%% @spec suite() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,30}}]. + +%%-------------------------------------------------------------------- +%% @spec init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + quicer_test_lib:generate_tls_certs(Config), + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_suite(Config0) -> term() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_group(GroupName, Config0) -> +%% term() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_testcase(TestCase, Config0) -> +%% term() | {save_config,Config1} | {fail,Reason} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%% @end +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% @spec all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +all() -> + quicer_test_lib:all_tcs(?MODULE). + +%%-------------------------------------------------------------------- +%% @spec TestCase(Config0) -> +%% ok | exit() | {skip,Reason} | {comment,Comment} | +%% {save_config,Config1} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% Comment = term() +%% @end +%%-------------------------------------------------------------------- +tc_open0(_) -> + {ok, H} = quicer_nif:open_connection(), + quicer:close_connection(H). + +tc_open1(_) -> + {ok, H} = quicer_nif:open_connection(#{}), + quicer:close_connection(H). + +tc_open2(Config0) -> + Config = maps:from_list(Config0), + {ok, H} = quicer_nif:open_connection(Config), + quicer:close_connection(H). + +tc_conn_basic(Config) -> + {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_basic(Config) end), + receive + {'DOWN', Ref, process, Pid, normal} -> + ok; + {'DOWN', Ref, process, Pid, Error} -> + ct:fail({run_error, Error}) + end. + +run_tc_conn_basic(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, {_, _}} = quicer:sockname(Conn), + ct:pal("closing connection : ~p", [Conn]), + ok = quicer:close_connection(Conn), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_basic_slow_start(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_slow_conn_server(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, {_, _}} = quicer:sockname(Conn), + ok = quicer:close_connection(Conn), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_basic_verify_peer(_Config)-> + {ok, Conn} = quicer:connect("google.com", 443, + [ {verify, verify_peer} + %, {sslkeylogfile, "/tmp/SSLKEYLOGFILE"} + , {peer_unidi_stream_count, 3} + , {alpn, ["h3"]}], 5000), + {ok, {_, _}} = quicer:sockname(Conn), + {ok, Info} = quicer:getopt(Conn, param_tls_handshake_info, quic_tls), + ct:pal("Handshake Info with Google: ~p", [Info]), + ok = quicer:close_connection(Conn), + ok. + +tc_conn_basic_verify_peer_no_cacert(Config)-> + %% Verify that the connection handshake should fail if + %% `verif_peer` is set but CA is unknown + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_slow_conn_server(Owner, Config, Port) + end), + receive listener_ready -> ok end, + + %% ErrorCode is different per platform + {error,transport_down, + #{error := _ErrorCode, + status := ErrorStatus}} = + quicer:connect("localhost", Port, + [ {verify, verify_peer} + , {peer_unidi_stream_count, 3} + , {alpn, ["sample"]}], 5000), + + ?assert(ErrorStatus =:= cert_untrusted_root orelse ErrorStatus =:= bad_certificate), + + receive + {quic, closed, _, _} -> + ct:fail("closed should be flushed") + after 500 -> + ok + end, + + SPid ! done, + ensure_server_exit_normal(Ref), + ok. + +tc_conn_timeout(Config)-> + Port = select_port(), + Owner = self(), + TOut = 10, + {SPid, Ref} = spawn_monitor( + fun() -> + simple_slow_conn_server(Owner, Config, Port, TOut*2) + end), + receive + listener_ready -> + {error, transport_down, #{error := 1, status := connection_idle}} + = quicer:connect("localhost", Port, default_conn_opts(), TOut), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_async_conn_timeout(Config)-> + Port = select_port(), + Owner = self(), + %% The value set here might not be the one actual in use + %% because loss detection takes many facts into account + %% A minimal value will be selected rather than this one + %% for more, look for QuicLossDetectionComputeProbeTimeout + Tout = 1000, + {SPid, Ref} = spawn_monitor( + fun() -> + simple_slow_conn_server(Owner, Config, Port, Tout*2) + end), + receive + listener_ready -> + {ok, H} = quicer:async_connect("localhost", Port, [{handshake_idle_timeout_ms, Tout} | + default_conn_opts()]), + receive + {quic, transport_shutdown, H, Reason} -> + %% silent local close + ?assertEqual(#{ error => 1 + , status => connection_idle}, Reason) + after Tout * 10 -> + ct:fail("conn didn't timeout") + end, + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_double_close(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, {_, _}} = quicer:sockname(Conn), + ok = quicer:close_connection(Conn), + SPid ! done, + quicer:async_close_connection(Conn), + %% Wait for it crash if it will + timer:sleep(1000), + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_other_port(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + ok = quicer:close_connection(Conn), + SPid ! done, + ok = ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_with_localaddr(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), + + {ok, CPort0} = gen_udp:open(0, [{ip, {127, 0, 0, 1}}]), + {ok, {{127, 0, 0, 1}, PortX}} = inet:sockname(CPort0), + ok = gen_udp:close(CPort0), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, [{param_conn_local_address, "127.0.0.1:" ++ integer_to_list(PortX)} + | default_conn_opts()], 5000), + ?assertEqual({ok, {{127,0,0,1}, PortX}}, quicer:sockname(Conn)), + ok = quicer:close_connection(Conn), + SPid ! done, + ok = ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_custom_ca(Config) -> + {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_custom_ca(Config) end), + receive + {'DOWN', Ref, process, Pid, normal} -> + ok; + {'DOWN', Ref, process, Pid, Error} -> + ct:fail({run_error, Error}) + end. + +run_tc_conn_custom_ca(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts_verify(Config, "ca"), 5000), + {ok, {_, _}} = quicer:sockname(Conn), + ct:pal("closing connection : ~p", [Conn]), + ok = quicer:close_connection(Conn), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_custom_ca_other(Config) -> + {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_custom_ca_other(Config) end), + receive + {'DOWN', Ref, process, Pid, normal} -> + ok; + {'DOWN', Ref, process, Pid, Error} -> + ct:fail({run_error, Error}) + end. + +run_tc_conn_custom_ca_other(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server_close(Owner, Config, Port) + end), + receive + listener_ready -> + {error,transport_down, + #{error := _ErrorCode, + status := bad_certificate}} = + quicer:connect("localhost", Port, + default_conn_opts_verify(Config, "other-ca"), + 5000), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_client_cert(Config) -> + {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_client_cert(Config) end), + receive + {'DOWN', Ref, process, Pid, normal} -> + ok; + {'DOWN', Ref, process, Pid, Error} -> + ct:fail({run_error, Error}) + end. + +run_tc_conn_client_cert(Config)-> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server_client_cert(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, + default_conn_opts_client_cert(Config, "ca"), + 5000), + {ok, {_, _}} = quicer:sockname(Conn), + ct:pal("closing connection : ~p", [Conn]), + ok = quicer:close_connection(Conn), + SPid ! done, + ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + +tc_conn_client_bad_cert(Config) -> + {Pid, Ref} = spawn_monitor(fun() -> run_tc_conn_client_bad_cert(Config) end), + receive + {'DOWN', Ref, process, Pid, normal} -> + ok; + {'DOWN', Ref, process, Pid, Error} -> + ct:fail({run_error, Error}) + end. + +run_tc_conn_client_bad_cert(Config)-> + Port = select_port(), + Owner = self(), + {_SPid, Ref} = spawn_monitor( + fun() -> + simple_conn_server_client_bad_cert(Owner, Config, Port) + end), + receive + listener_ready -> + {ok, Conn} = quicer:connect( + "localhost", Port, + default_conn_opts_bad_client_cert(Config, "ca"), + 5000), + case quicer:start_stream(Conn, []) of + {error, stm_open_error, aborted} -> + %% Depending on the timing, connection open could fail already. + ok; + {ok, Stm} -> + case quicer:send(Stm, <<"ping">>) of + {ok, 4} -> ok; + {error, cancelled} -> ok; + {error, stm_send_error, aborted} -> ok + end, + receive + {quic, transport_shutdown, _Ref, + #{error := _ErrorCode, status := bad_certificate}} -> + _ = flush([]) + after + 2000 -> + Other = flush([]), + ct:fail("Unexpected Msg ~p", [Other]) + end, + ensure_server_exit_normal(Ref) + end + after 1000 -> + ct:fail("timeout") + end. + +%% @doc check old owner is demonitored. +tc_conn_controlling_process_demon(Config) -> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + Parent = self(), + {OldOwner, MonRef} = spawn_monitor( + fun() -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + Res = quicer:controlling_process(Conn, Parent), + exit({Res, Conn}) + end), + Conn = receive + {'DOWN', MonRef, process, OldOwner, {Res, TheConn}} -> + ct:pal("Old Owner is down, mon res: ~p", [Res]), + TheConn + end, + %% Try set owner back to dead previous owner, should fail + ?assertEqual({error, owner_dead}, quicer:controlling_process(Conn, OldOwner)), + %% rollback to this owner. + + {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), + {ok, 11} = quicer:send(Stm, <<"ping_active">>), + {ok, _} = quicer:recv(Stm, 11), + SPid ! done, + + {NewOwner2, MonRef2} = spawn_monitor(fun() -> + receive stop -> ok end + end), + ok = quicer:controlling_process(Conn, NewOwner2), + NewOwner2 ! stop, + receive + {'DOWN', MonRef2, process, NewOwner2, normal} -> ok + end, + ?assertNotMatch({ok, _}, quicer:send(Stm, <<"ping_active">>)), + quicer:async_shutdown_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0), + SPid ! done, + ensure_server_exit_normal(Ref) + after 6000 -> + ct:fail("timeout") + end. + +tc_conn_controlling_process(Config) -> + Port = select_port(), + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), + ok = quicer:controlling_process(Conn, self()), + {ok, 11} = quicer:send(Stm, <<"ping_active">>), + {ok, _} = quicer:recv(Stm, 11), + {NewOwner, MonRef} = spawn_monitor( + fun() -> + receive + {quic, closed, Conn, _Flags} -> + ok + end + end), + ok = quicer:controlling_process(Conn, NewOwner), + %% Trigger *async* connection shutdown since I am not the conn owner + quicer:async_shutdown_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0), + receive + {'DOWN', MonRef, process, NewOwner, normal} -> + SPid ! done + end, + ensure_server_exit_normal(Ref) + after 6000 -> + ct:fail("timeout") + end. + +tc_conn_opt_ideal_processor(Config) -> + Port = select_port(), + Owner = self(), + {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Stm} = quicer:start_stream(Conn, []), + {ok, 4} = quicer:send(Stm, <<"ping">>), + {ok, Processor} = quicer:getopt(Conn, param_conn_ideal_processor), + ?assert(is_integer(Processor)), + ok = quicer:close_connection(Conn) + after 5000 -> + ct:fail("listener_timeout") + end. + +tc_conn_opt_share_udp_binding(Config) -> + Port = select_port(), + Owner = self(), + {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Stm} = quicer:start_stream(Conn, []), + {ok, 4} = quicer:send(Stm, <<"ping">>), + {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), + ?assert(is_boolean(IsShared)), + {error, invalid_state} = quicer:setopt(Conn, param_conn_share_udp_binding, not IsShared), + {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), + ok = quicer:close_connection(Conn) + after 5000 -> + ct:fail("listener_timeout") + end. + +tc_conn_opt_local_bidi_stream_count(Config) -> + Port = select_port(), + Owner = self(), + {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Stm} = quicer:start_stream(Conn, []), + {ok, 4} = quicer:send(Stm, <<"ping">>), + {ok, Cnt} = quicer:getopt(Conn, param_conn_local_bidi_stream_count), + ?assert(is_integer(Cnt)), + {error, invalid_parameter} = quicer:setopt(Conn, param_conn_local_bidi_stream_count, Cnt + 2), + ok = quicer:close_connection(Conn) + after 5000 -> + ct:fail("listener_timeout") + end. + +tc_conn_opt_local_uni_stream_count(Config) -> + Port = select_port(), + Owner = self(), + {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Stm} = quicer:start_stream(Conn, []), + {ok, 4} = quicer:send(Stm, <<"ping">>), + {ok, Cnt} = quicer:getopt(Conn, param_conn_local_unidi_stream_count), + ?assert(is_integer(Cnt)), + {error, invalid_parameter} = quicer:setopt(Conn, param_conn_local_unidi_stream_count, Cnt + 2), + ok = quicer:close_connection(Conn) + after 5000 -> + ct:fail("listener_timeout") + end. + + +%%% +%%% Helpers +%%% +select_port() -> + select_free_port(quic). + +simple_conn_server(Owner, Config, Port) -> + {ok, L} = quicer:listen(Port, default_listen_opts(Config)), + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 1000), + {ok, Conn} = quicer:handshake(Conn), + simple_conn_server_loop(L, Conn, Owner). + +simple_conn_server_loop(L, Conn, Owner) -> + receive + done -> + quicer:close_connection(Conn), + quicer:close_listener(L), + ok; + peercert -> + CertResp = quicer:peercert(Conn), + Owner ! {self(), peercert, CertResp}, + simple_conn_server_loop(L, Conn, Owner); + {quic, shutdown, Conn} -> + quicer:close_connection(Conn), + quicer:close_listener(L) + end. + +simple_conn_server_close(Owner, Config, Port) -> + {ok, L} = quicer:listen(Port, default_listen_opts(Config)), + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 1000), + {error, closed} = quicer:handshake(Conn), + receive + done -> + quicer:close_listener(L), + ok; + {quic, shutdown, Conn} -> + quicer:close_listener(L) + end. + +simple_conn_server_client_cert(Owner, Config, Port) -> + {ok, L} = quicer:listen(Port, default_listen_opts_client_cert(Config)), + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 1000), + case quicer:handshake(Conn) of + {ok, Conn} -> + simple_conn_server_client_cert_loop(L, Conn, Owner); + {error, closed} -> + receive done -> + quicer:close_listener(L) + end + end. + +simple_conn_server_client_cert_loop(L, Conn, Owner) -> + receive + done -> + quicer:close_listener(L), + ok; + peercert -> + {ok, PeerCert} = quicer:peercert(Conn), + Owner ! {self(), peercert, PeerCert}, + simple_conn_server_client_cert_loop(L, Conn, Owner); + {quic, shutdown, Conn, _ErrorCode} -> + quicer:close_connection(Conn), + quicer:close_listener(L) + end. + +simple_conn_server_client_bad_cert(Owner, Config, Port) -> + {ok, L} = quicer:listen(Port, default_listen_opts_client_cert(Config)), + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 1000), + {error, closed} = quicer:handshake(Conn), + quicer:close_listener(L). + +simple_slow_conn_server(Owner, Config, Port) -> + simple_slow_conn_server(Owner, Config, Port, 0). +simple_slow_conn_server(Owner, Config, Port, HandshakeDelay) -> + {ok, L} = quicer:listen(Port, [ {handshake_idle_timeout_ms, HandshakeDelay*2+10} + | default_listen_opts(Config)]), + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 5000), + ct:pal("~p new conn ~p", [?FUNCTION_NAME, Conn]), + timer:sleep(HandshakeDelay), + ok = quicer:async_handshake(Conn), + ct:pal("~p handshake ~p", [?FUNCTION_NAME, Conn]), + receive + {quic, connected, Conn, _} -> + ct:pal("~p Connected ~p", [?FUNCTION_NAME, Conn]), + ok; + {quic, closed, Conn, _Flags} -> + %% for timeout test + ct:pal("~p conn ~p closed", [?FUNCTION_NAME, Conn]), + ok + end, + %% test what happens if handshake twice + {error, invalid_state} = quicer:handshake(Conn), + receive done -> + quicer:close_listener(L), + ok + end. + + +default_conn_opts_verify(Config, Ca) -> + DataDir = ?config(data_dir, Config), + CACertFile = filename:join(DataDir, Ca) ++ ".pem", + [{verify, peer}, + {cacertfile, CACertFile} | + tl(default_conn_opts())]. + +default_conn_opts_client_cert(Config, Ca) -> + DataDir = ?config(data_dir, Config), + [{keyfile, filename:join(DataDir, "client.key")}, + {certfile, filename:join(DataDir, "client.pem")}| + default_conn_opts_verify(Config, Ca)]. + +default_conn_opts_bad_client_cert(Config, Ca) -> + DataDir = ?config(data_dir, Config), + [{keyfile, filename:join(DataDir, "other-client.key")}, + {certfile, filename:join(DataDir, "other-client.pem")}| + default_conn_opts_verify(Config, Ca)]. + +default_listen_opts_client_cert(Config) -> + DataDir = ?config(data_dir, Config), + [ {cacertfile, filename:join(DataDir, "ca.pem")} + , {verify, peer} | + tl(default_listen_opts(Config)) ]. + + +echo_server(Owner, Config, Port)-> + put(echo_server_test_coordinator, Owner), + case quicer:listen(Port, default_listen_opts(Config)) of + {ok, L} -> + Owner ! listener_ready, + {ok, Conn} = quicer:accept(L, [], 5000), + {ok, Conn} = quicer:async_accept_stream(Conn, []), + {ok, Conn} = quicer:handshake(Conn), + ct:pal("echo server conn accepted", []), + receive + {quic, new_stream, Stm, _Props} -> + {ok, Conn} = quicer:async_accept_stream(Conn, []); + {flow_ctl, BidirCount, UniDirCount} -> + ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [BidirCount, UniDirCount]), + quicer:setopt(Conn, param_conn_settings, #{peer_bidi_stream_count => BidirCount, + peer_unidi_stream_count => UniDirCount}), + receive {quic, new_stream, Stm, _Props} -> + {ok, Conn} = quicer:async_accept_stream(Conn, []) + end + end, + ct:pal("echo server stream accepted", []), + echo_server_stm_loop(L, Conn, [Stm]); + {error, listener_start_error, 200000002} -> + ct:pal("echo_server: listener_start_error", []), + timer:sleep(100), + echo_server(Owner, Config, Port) + end. + +echo_server_stm_loop(L, Conn, Stms) -> + receive + {quic, <<"Abort">>, Stm, #{flags := _Flag}} -> + quicer:async_shutdown_stream(Stm, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 1), + echo_server_stm_loop(L, Conn, Stms); + {quic, Bin, Stm, #{flags := Flag}} when is_binary(Bin) -> + SendFlag = case (Flag band ?QUIC_RECEIVE_FLAG_FIN) > 0 of + true -> ?QUICER_SEND_FLAG_SYNC bor ?QUIC_SEND_FLAG_FIN; + false -> ?QUICER_SEND_FLAG_SYNC + end, + case quicer:send(Stm, Bin, SendFlag) of + {error, stm_send_error, aborted} -> + ct:pal("echo server: send aborted: ~p ", [Bin]); + {error, stm_send_error, invalid_state} -> + {ok, RetStream} = + quicer:start_stream(Conn, [{open_flag, ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}]), + quicer:send(RetStream, Bin); + {error, cancelled} -> + ct:pal("echo server: send cancelled: ~p ", [Bin]), + cancelled; + {ok, _} -> + ok + end, + echo_server_stm_loop(L, Conn, Stms); + {quic, peer_send_aborted, Stm, _Error} -> + ct:pal("echo server peer_send_aborted", []), + quicer:close_stream(Stm), + echo_server_stm_loop(L, Conn, Stms); + {quic, peer_send_shutdown, Stm, undefined} -> + ct:pal("echo server peer_send_shutdown", []), + quicer:close_stream(Stm), + echo_server_stm_loop(L, Conn, Stms); + {quic, transport_shutdown, Conn, #{status := ErrorAtom}} -> + ct:pal("echo server transport_shutdown due to ~p", [ErrorAtom]), + get(echo_server_test_coordinator) ! {echo_server_transport_shutdown, ErrorAtom}, + echo_server_stm_loop(L, Conn, Stms); + {quic, shutdown, Conn, ErrorCode} -> + ct:pal("echo server conn shutdown ~p due to ~p", [Conn, ErrorCode]), + quicer:close_connection(Conn), + echo_server_stm_loop(L, Conn, Stms); + {quic, closed, Conn, _Flags} -> + ct:pal("echo server Conn closed", []), + echo_server_stm_loop(L, Conn, Stms); + {quic, stream_closed, Stm, Flag} -> + ct:pal("echo server stream closed ~p", [Flag]), + echo_server_stm_loop(L, Conn, Stms -- [Stm]); + {set_stm_cnt, N } -> + ct:pal("echo_server: set max stream count: ~p", [N]), + ok = quicer:setopt(Conn, param_conn_settings, #{peer_bidi_stream_count => N}), + {ok, NewStm} = quicer:accept_stream(Conn, []), + echo_server_stm_loop(L, Conn, [NewStm | Stms]); + {peer_addr, From} -> + From ! {peer_addr, quicer:peername(Conn)}, + echo_server_stm_loop(L, Conn, Stms); + {flow_ctl, BidirCount, UniDirCount} -> + ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [BidirCount, UniDirCount]), + quicer:setopt(Conn, param_conn_settings, #{peer_bidi_stream_count => BidirCount, + peer_unidi_stream_count => UniDirCount}), + {ok, Conn} = quicer:async_accept_stream(Conn, []), + echo_server_stm_loop(L, Conn, Stms); + {quic, new_stream, NewStm, #{flags := Flags}} -> + NewStmList = case quicer:is_unidirectional(Flags) of + true -> + ct:pal("echo server: new incoming unidirectional stream"), + {ok, ReturnStm} = quicer:start_stream(Conn, [{open_flag, ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}]), + [{NewStm, ReturnStm} | Stms]; + false -> + ct:pal("echo server: new incoming binary stream"), + [NewStm | Stms] + end, + echo_server_stm_loop(L, Conn, NewStmList); + done -> + ct:pal("echo server shutting down", []), + quicer:async_close_connection(Conn), + quicer:close_listener(L) + end. diff --git a/test/quicer_test_lib.erl b/test/quicer_test_lib.erl index 6c730d5a..a8c8cfdb 100644 --- a/test/quicer_test_lib.erl +++ b/test/quicer_test_lib.erl @@ -27,7 +27,10 @@ receive_all/0, recv_term_from_stream/1, encode_stream_term/1, - select_free_port/1 + select_free_port/1, + flush/1, + ensure_server_exit_normal/1, + ensure_server_exit_normal/2 ]). @@ -319,6 +322,29 @@ generate_tls_certs(Config) -> filename:join(DataDir, "two-intermediates-bundle.pem") ])). +-spec flush([term()]) -> [term()]. +flush(Acc) -> + receive + Other -> + flush([Other|Acc]) + after + 0 -> + lists:reverse(Acc) + end. + +-spec ensure_server_exit_normal(reference()) -> ok. +ensure_server_exit_normal(MonRef) -> + ensure_server_exit_normal(MonRef, 5000). +ensure_server_exit_normal(MonRef, Timeout) -> + receive + {'DOWN', MonRef, process, _, normal} -> + ok; + {'DOWN', MonRef, process, _, Other} -> + ct:fail("server exits abnormally ~p ", [Other]) + after Timeout -> + ct:fail("server still running", []) + end. + %%%_* Emacs ==================================================================== %%% Local Variables: From 061323863002ec214e68b750172b0ffe41c616b2 Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 8 Sep 2023 12:39:34 +0200 Subject: [PATCH 5/9] refactor(conn): move parse keylogfile to quicer_tls --- c_src/quicer_connection.c | 34 ++-------------------- c_src/quicer_tls.c | 59 +++++++++++++++++++++++++++++++++++++++ c_src/quicer_tls.h | 4 +++ 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index 288ff070..de825af6 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -709,38 +709,8 @@ async_connect3(ErlNifEnv *env, assert(c_ctx->is_closed); c_ctx->is_closed = FALSE; // connection opened. - ERL_NIF_TERM essl_keylogfile; - if (enif_get_map_value( - env, eoptions, ATOM_SSL_KEYLOGFILE_NAME, &essl_keylogfile)) - { - char *keylogfile = CXPLAT_ALLOC_NONPAGED(PATH_MAX, QUICER_TRACE); - if (enif_get_string( - env, essl_keylogfile, keylogfile, PATH_MAX, ERL_NIF_LATIN1) - > 0) - { - QUIC_TLS_SECRETS *TlsSecrets = CXPLAT_ALLOC_NONPAGED( - sizeof(QUIC_TLS_SECRETS), QUICER_TLS_SECRETS); - - CxPlatZeroMemory(TlsSecrets, sizeof(QUIC_TLS_SECRETS)); - Status = MsQuic->SetParam(c_ctx->Connection, - QUIC_PARAM_CONN_TLS_SECRETS, - sizeof(QUIC_TLS_SECRETS), - TlsSecrets); - if (QUIC_FAILED(Status)) - { - fprintf(stderr, - "failed to enable secret logging: %s", - QuicStatusToString(Status)); - } - c_ctx->TlsSecrets = TlsSecrets; - c_ctx->ssl_keylogfile = keylogfile; - } - - else - { - fprintf(stderr, "failed to read string ssl_keylogfile"); - } - } + // optional set sslkeylogfile + parse_sslkeylogfile_option(env, eoptions, c_ctx); ERL_NIF_TERM evalue; if (enif_get_map_value( diff --git a/c_src/quicer_tls.c b/c_src/quicer_tls.c index 974724fd..29a7f347 100644 --- a/c_src/quicer_tls.c +++ b/c_src/quicer_tls.c @@ -217,3 +217,62 @@ free_certificate(QUIC_CREDENTIAL_CONFIG *cc) QUICER_CERTIFICATE_FILE_PROTECTED); } } + +/* + * Parse 'sslkeylogfile' option and set QUIC_PARAM_CONN_TLS_SECRETS conn + * options for sslkeylogfile dump. + * + * alloc and update: + * c_ctx->TlsSecrets = TlsSecrets; + * c_ctx->ssl_keylogfile = keylogfile; + * + * usually they are not inuse (NULL), so we use heap memory. + * Caller should ensure they are freed after use. + * + */ +void +parse_sslkeylogfile_option(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx) +{ + QUIC_STATUS Status; + + char *keylogfile = str_from_map( + env, ATOM_SSL_KEYLOGFILE_NAME, &eoptions, NULL, PATH_MAX + 1); + + if (!keylogfile) + { + return; + } + + // Allocate the TLS secrets + QUIC_TLS_SECRETS *TlsSecrets + = CXPLAT_ALLOC_NONPAGED(sizeof(QUIC_TLS_SECRETS), QUICER_TLS_SECRETS); + + if (!TlsSecrets) + { + return; + } + + CxPlatZeroMemory(TlsSecrets, sizeof(QUIC_TLS_SECRETS)); + + // Set conn opt QUIC_PARAM_CONN_TLS_SECRETS + if (QUIC_FAILED(Status = MsQuic->SetParam(c_ctx->Connection, + QUIC_PARAM_CONN_TLS_SECRETS, + sizeof(QUIC_TLS_SECRETS), + TlsSecrets))) + { + //unlikely + CXPLAT_FREE(keylogfile, QUICER_TRACE); + keylogfile = NULL; + CXPLAT_FREE(TlsSecrets, QUICER_TLS_SECRETS); + TlsSecrets = NULL; + fprintf(stderr, + "failed to enable secret logging: %s", + QuicStatusToString(Status)); + } + + // @TODO: check if old ssl_keylogfile/TlsSecrets is set, free it? + c_ctx->TlsSecrets = TlsSecrets; + c_ctx->ssl_keylogfile = keylogfile; +} diff --git a/c_src/quicer_tls.h b/c_src/quicer_tls.h index 9e09540e..e82c1c32 100644 --- a/c_src/quicer_tls.h +++ b/c_src/quicer_tls.h @@ -37,4 +37,8 @@ BOOLEAN build_trustedstore(const char *cacertfile, X509_STORE **trusted_store); void free_certificate(QUIC_CREDENTIAL_CONFIG *cc); + +void parse_sslkeylogfile_option(ErlNifEnv *env, + ERL_NIF_TERM options, + QuicerConnCTX *c_ctx); #endif // QUICER_TLS_H_ From 1a240cb613dfcddca4ffc7afb90b2fff86cee22c Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 8 Sep 2023 15:03:50 +0200 Subject: [PATCH 6/9] refactor: more conn opt parser funs - parse_conn_local_address - parse_conn_resume_ticket - parse_conn_disable_1rtt_encryption - parse_conn_event_mask --- CMakeLists.txt | 2 +- c_src/quicer_connection.c | 174 ++++++++++++++++++++++++++------------ c_src/quicer_tls.c | 2 +- test/quicer_SUITE.erl | 12 +-- 4 files changed, 126 insertions(+), 64 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a881d99..e9c5d4c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ elseif (CMAKE_SYSTEM_NAME MATCHES Darwin) endif() # @todo clean compiler warnings in the code -target_compile_options(quicer_static PUBLIC "-ggdb3" "-Wno-unused-variable") +target_compile_options(quicer_static PUBLIC "-ggdb3") if (DEFINED ENV{QUICER_TEST_COVER}) add_compile_options("-fprofile-arcs" "-ftest-coverage") diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index de825af6..13850b45 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -99,6 +99,22 @@ parse_registration(ErlNifEnv *env, ERL_NIF_TERM options, QuicerRegistrationCTX **r_ctx); +ERL_NIF_TERM parse_conn_local_address(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx); + +ERL_NIF_TERM parse_conn_resume_ticket(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx); + +ERL_NIF_TERM parse_conn_disable_1rtt_encryption(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx); + +ERL_NIF_TERM parse_conn_event_mask(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx); + ERL_NIF_TERM peercert1(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) { @@ -561,15 +577,13 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) ERL_NIF_TERM async_connect3(ErlNifEnv *env, __unused_parm__ int argc, - __unused_parm__ const ERL_NIF_TERM argv[]) + const ERL_NIF_TERM argv[]) { QUIC_STATUS Status; - ERL_NIF_TERM ehost = argv[0]; ERL_NIF_TERM eport = argv[1]; ERL_NIF_TERM eoptions = argv[2]; ERL_NIF_TERM eHandle = ATOM_UNDEFINED; - ERL_NIF_TERM NST; // New Session Ticket // Usually we should not get this error // If we get it is internal logic error ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_INTERNAL_ERROR); @@ -712,72 +726,36 @@ async_connect3(ErlNifEnv *env, // optional set sslkeylogfile parse_sslkeylogfile_option(env, eoptions, c_ctx); - ERL_NIF_TERM evalue; - if (enif_get_map_value( - env, eoptions, ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, &evalue)) + if (!IS_SAME_TERM(ATOM_OK, + (res = parse_conn_local_address(env, eoptions, c_ctx)))) { - if (!IS_SAME_TERM(ATOM_OK, - set_connection_opt(env, - c_ctx, - ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, - evalue, - ATOM_FALSE))) - { - res = ERROR_TUPLE_2(ATOM_CONN_OPEN_ERROR); - goto Error; - } + goto Error; } - if (enif_get_map_value(env, - eoptions, - ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, - &evalue)) + if (!IS_SAME_TERM(ATOM_OK, + (res = parse_conn_resume_ticket(env, eoptions, c_ctx)))) { - if (!IS_SAME_TERM( - ATOM_OK, - set_connection_opt(env, - c_ctx, - ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, - evalue, - ATOM_FALSE))) - { - res = ERROR_TUPLE_2(ATOM_CONN_OPEN_ERROR); - goto Error; - } + goto Error; } - if (enif_get_map_value(env, eoptions, ATOM_NST, &NST)) + if (!IS_SAME_TERM( + ATOM_OK, + (res = parse_conn_disable_1rtt_encryption(env, eoptions, c_ctx)))) { - // Resume connection with NST binary - // - // - ErlNifBinary ticket; - if (!enif_inspect_binary(env, NST, &ticket) || ticket.size > UINT32_MAX) - { - res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); - goto Error; - } - else - { - if (QUIC_FAILED(Status - = MsQuic->SetParam(c_ctx->Connection, - QUIC_PARAM_CONN_RESUMPTION_TICKET, - ticket.size, - ticket.data))) - { - res = ERROR_TUPLE_3(ATOM_ERROR_NOT_FOUND, ATOM_STATUS(Status)); - goto Error; - } - } + goto Error; } - // This is optional - get_uint32_from_map(env, eoptions, ATOM_QUIC_EVENT_MASK, &c_ctx->event_mask); + if (!IS_SAME_TERM(ATOM_OK, + (res = parse_conn_event_mask(env, eoptions, c_ctx)))) + { + goto Error; + } // @TODO client async_connect_3 should able to take a config_resource as // input ERL TERM so that we don't need to call ClientLoadConfiguration // assert(!c_ctx->is_closed && c_ctx->Connection); + if (QUIC_FAILED(Status = MsQuic->ConnectionStart( c_ctx->Connection, c_ctx->config_resource->Configuration, @@ -811,7 +789,6 @@ async_connect3(ErlNifEnv *env, Error: if (c_ctx->Connection) { // when is opened - /* We should not call *destroy_c_ctx* from here. because it could cause race cond: @@ -1598,6 +1575,91 @@ handle_connection_event_peer_certificate_received(QuicerConnCTX *c_ctx, /* @TODO validate SNI */ } +/* +** parse optional conn opt: local addr +*/ +ERL_NIF_TERM +parse_conn_local_address(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx) +{ + + ERL_NIF_TERM evalue; + ERL_NIF_TERM res = ATOM_OK; + if (enif_get_map_value( + env, eoptions, ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, &evalue)) + { + return set_connection_opt( + env, c_ctx, ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, evalue, ATOM_FALSE); + } + return res; +} + +/* +** parse optional conn opt: disable_1rtt_encryption +*/ +ERL_NIF_TERM +parse_conn_disable_1rtt_encryption(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx) +{ + ERL_NIF_TERM evalue; + if (enif_get_map_value(env, + eoptions, + ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, + &evalue)) + { + return set_connection_opt(env, + c_ctx, + ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, + evalue, + ATOM_FALSE); + } + return ATOM_OK; +} + +/* +** parse optional conn opt: nst (resume_ticket) +** +** resume connection with NST binary +*/ +ERL_NIF_TERM +parse_conn_resume_ticket(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx) +{ + ERL_NIF_TERM evalue; + if (enif_get_map_value(env, eoptions, ATOM_NST, &evalue)) + { + return set_connection_opt(env, + c_ctx, + ATOM_QUIC_PARAM_CONN_RESUMPTION_TICKET, + evalue, + ATOM_FALSE); + } + return ATOM_OK; +} + +/* + * parse optional conn opt: quic_event_mask + * + */ +ERL_NIF_TERM +parse_conn_event_mask(ErlNifEnv *env, + ERL_NIF_TERM eoptions, + QuicerConnCTX *c_ctx) +{ + ERL_NIF_TERM evalue; + if (enif_get_map_value(env, eoptions, ATOM_QUIC_EVENT_MASK, &evalue)) + { + if (!enif_get_uint(env, evalue, &(c_ctx->event_mask))) + { + return ERROR_TUPLE_2(ATOM_QUIC_EVENT_MASK); + } + } + return ATOM_OK; +} + ///_* Emacs ///==================================================================== /// Local Variables: diff --git a/c_src/quicer_tls.c b/c_src/quicer_tls.c index 29a7f347..2c1f2217 100644 --- a/c_src/quicer_tls.c +++ b/c_src/quicer_tls.c @@ -262,7 +262,7 @@ parse_sslkeylogfile_option(ErlNifEnv *env, sizeof(QUIC_TLS_SECRETS), TlsSecrets))) { - //unlikely + // unlikely CXPLAT_FREE(keylogfile, QUICER_TRACE); keylogfile = NULL; CXPLAT_FREE(TlsSecrets, QUICER_TLS_SECRETS); diff --git a/test/quicer_SUITE.erl b/test/quicer_SUITE.erl index fd8ea6e8..03ceb506 100644 --- a/test/quicer_SUITE.erl +++ b/test/quicer_SUITE.erl @@ -1285,15 +1285,15 @@ tc_setopt(Config) -> tc_setopt_bad_opt(_Config)-> Port = select_port(), - {error, param_error} = quicer:connect("localhost", Port, - [{nst, foobar} %% BAD opt - | default_conn_opts()], 5000). + {error, badarg} = quicer:connect("localhost", Port, + [{nst, foobar} %% BAD opt + | default_conn_opts()], 5000). tc_setopt_bad_nst(_Config)-> Port = select_port(), - {error, nst_not_found} = quicer:connect("localhost", Port, - [{nst, <<"">>} - | default_conn_opts()], 5000). + {error, invalid_parameter} = quicer:connect("localhost", Port, + [{nst, <<"">>} + | default_conn_opts()], 5000). tc_setopt_config_settings(Config) -> Port = select_port(), From 60d7ac7555bccd95eb623983efd0a6710271fc42 Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 8 Sep 2023 15:15:46 +0200 Subject: [PATCH 7/9] test(conn): mux global_reg and suite reg --- test/quicer_connection_SUITE.erl | 56 ++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/test/quicer_connection_SUITE.erl b/test/quicer_connection_SUITE.erl index 3530e638..326443c1 100644 --- a/test/quicer_connection_SUITE.erl +++ b/test/quicer_connection_SUITE.erl @@ -68,8 +68,12 @@ end_per_suite(_Config) -> %% Reason = term() %% @end %%-------------------------------------------------------------------- -init_per_group(_GroupName, Config) -> - Config. +init_per_group(global_reg, Config) -> + Config; +init_per_group(suite_reg, Config) -> + {ok, SReg} = quicer:new_registration(atom_to_list(?MODULE), + quic_execution_profile_max_throughput), + [{quic_registration, SReg} | Config]. %%-------------------------------------------------------------------- %% @spec end_per_group(GroupName, Config0) -> @@ -78,8 +82,10 @@ init_per_group(_GroupName, Config) -> %% Config0 = Config1 = [tuple()] %% @end %%-------------------------------------------------------------------- +end_per_group(suite_reg, Config) -> + quicer:shutdown_registration(proplists:get_value(quic_registration, Config)); end_per_group(_GroupName, _Config) -> - ok. + ok. %%-------------------------------------------------------------------- %% @spec init_per_testcase(TestCase, Config0) -> @@ -117,7 +123,10 @@ end_per_testcase(_TestCase, _Config) -> %% @end %%-------------------------------------------------------------------- groups() -> - []. + TCs = quicer_test_lib:all_tcs(?MODULE), + [ {global_reg, [], TCs} + , {suite_reg, [], TCs} + ]. %%-------------------------------------------------------------------- %% @spec all() -> GroupsAndTestCases | {skip,Reason} @@ -128,7 +137,9 @@ groups() -> %% @end %%-------------------------------------------------------------------- all() -> - quicer_test_lib:all_tcs(?MODULE). + [ {group, global_reg} + , {group, suite_reg} + ]. %%-------------------------------------------------------------------- %% @spec TestCase(Config0) -> @@ -170,7 +181,7 @@ run_tc_conn_basic(Config)-> end), receive listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), {ok, {_, _}} = quicer:sockname(Conn), ct:pal("closing connection : ~p", [Conn]), ok = quicer:close_connection(Conn), @@ -189,7 +200,7 @@ tc_conn_basic_slow_start(Config)-> end), receive listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), {ok, {_, _}} = quicer:sockname(Conn), ok = quicer:close_connection(Conn), SPid ! done, @@ -198,12 +209,12 @@ tc_conn_basic_slow_start(Config)-> ct:fail("timeout") end. -tc_conn_basic_verify_peer(_Config)-> +tc_conn_basic_verify_peer(Config)-> {ok, Conn} = quicer:connect("google.com", 443, [ {verify, verify_peer} %, {sslkeylogfile, "/tmp/SSLKEYLOGFILE"} , {peer_unidi_stream_count, 3} - , {alpn, ["h3"]}], 5000), + , {alpn, ["h3"]} | Config], 5000), {ok, {_, _}} = quicer:sockname(Conn), {ok, Info} = quicer:getopt(Conn, param_tls_handshake_info, quic_tls), ct:pal("Handshake Info with Google: ~p", [Info]), @@ -228,7 +239,7 @@ tc_conn_basic_verify_peer_no_cacert(Config)-> quicer:connect("localhost", Port, [ {verify, verify_peer} , {peer_unidi_stream_count, 3} - , {alpn, ["sample"]}], 5000), + , {alpn, ["sample"]} | Config], 5000), ?assert(ErrorStatus =:= cert_untrusted_root orelse ErrorStatus =:= bad_certificate), @@ -254,7 +265,7 @@ tc_conn_timeout(Config)-> receive listener_ready -> {error, transport_down, #{error := 1, status := connection_idle}} - = quicer:connect("localhost", Port, default_conn_opts(), TOut), + = quicer:connect("localhost", Port, default_conn_opts(Config), TOut), SPid ! done, ensure_server_exit_normal(Ref) after 1000 -> @@ -276,7 +287,7 @@ tc_async_conn_timeout(Config)-> receive listener_ready -> {ok, H} = quicer:async_connect("localhost", Port, [{handshake_idle_timeout_ms, Tout} | - default_conn_opts()]), + default_conn_opts(Config)]), receive {quic, transport_shutdown, H, Reason} -> %% silent local close @@ -300,7 +311,7 @@ tc_conn_double_close(Config)-> end), receive listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), {ok, {_, _}} = quicer:sockname(Conn), ok = quicer:close_connection(Conn), SPid ! done, @@ -318,7 +329,7 @@ tc_conn_other_port(Config)-> {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), ok = quicer:close_connection(Conn), SPid ! done, ok = ensure_server_exit_normal(Ref) @@ -337,7 +348,7 @@ tc_conn_with_localaddr(Config)-> receive listener_ready -> {ok, Conn} = quicer:connect("127.0.0.1", Port, [{param_conn_local_address, "127.0.0.1:" ++ integer_to_list(PortX)} - | default_conn_opts()], 5000), + | default_conn_opts(Config)], 5000), ?assertEqual({ok, {{127,0,0,1}, PortX}}, quicer:sockname(Conn)), ok = quicer:close_connection(Conn), SPid ! done, @@ -491,7 +502,7 @@ tc_conn_controlling_process_demon(Config) -> Parent = self(), {OldOwner, MonRef} = spawn_monitor( fun() -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), Res = quicer:controlling_process(Conn, Parent), exit({Res, Conn}) end), @@ -531,7 +542,7 @@ tc_conn_controlling_process(Config) -> {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(Config), 5000), {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), ok = quicer:controlling_process(Conn, self()), {ok, 11} = quicer:send(Stm, <<"ping_active">>), @@ -561,7 +572,7 @@ tc_conn_opt_ideal_processor(Config) -> {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(Config), 5000), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, Processor} = quicer:getopt(Conn, param_conn_ideal_processor), @@ -577,7 +588,7 @@ tc_conn_opt_share_udp_binding(Config) -> {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(Config), 5000), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), @@ -595,7 +606,7 @@ tc_conn_opt_local_bidi_stream_count(Config) -> {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(Config), 5000), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, Cnt} = quicer:getopt(Conn, param_conn_local_bidi_stream_count), @@ -612,7 +623,7 @@ tc_conn_opt_local_uni_stream_count(Config) -> {_SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), receive listener_ready -> - {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(Config), 5000), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, Cnt} = quicer:getopt(Conn, param_conn_local_unidi_stream_count), @@ -857,3 +868,6 @@ echo_server_stm_loop(L, Conn, Stms) -> quicer:async_close_connection(Conn), quicer:close_listener(L) end. + +default_conn_opts(Config) -> + default_conn_opts() ++ Config. From 3be3c7dc3ae7c4318e99a8575ef149443e824e48 Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 8 Sep 2023 16:46:12 +0200 Subject: [PATCH 8/9] fix(conn): protect opened c_ctx if reuse handle --- c_src/quicer_connection.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index 13850b45..a8c95a6a 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -608,7 +608,6 @@ async_connect3(ErlNifEnv *env, if (enif_get_map_value(env, eoptions, ATOM_HANDLE, &eHandle)) { // Reuse c_ctx from existing connecion handle - // if (enif_get_resource(env, eHandle, ctx_connection_t, (void **)&c_ctx)) { assert(c_ctx->is_closed); @@ -622,7 +621,6 @@ async_connect3(ErlNifEnv *env, { Registration = GRegistration; } - // @TODO we should take lock here } else { @@ -668,6 +666,13 @@ async_connect3(ErlNifEnv *env, } } + + if (is_reuse_handle) + { + enif_mutex_lock(c_ctx->lock); + } + + assert(c_ctx->owner); // allocate config_resource for client connection if (NULL == (c_ctx->config_resource = init_config_ctx())) @@ -784,6 +789,10 @@ async_connect3(ErlNifEnv *env, enif_monitor_process(NULL, c_ctx, &c_ctx->owner->Pid, &c_ctx->owner_mon); eHandle = enif_make_resource(env, c_ctx); + if(is_reuse_handle) + { + enif_mutex_unlock(c_ctx->lock); + } return SUCCESS(eHandle); Error: @@ -832,6 +841,12 @@ async_connect3(ErlNifEnv *env, } // Error exit, it must be closed or Handle is NULL assert(c_ctx->is_closed || NULL == c_ctx->Connection); + + if(is_reuse_handle) + { + enif_mutex_unlock(c_ctx->lock); + } + return res; } From 6f047c70424d9741946f93e6d0e20261869a2fb7 Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 8 Sep 2023 22:55:39 +0200 Subject: [PATCH 9/9] fix(conn): deadlock when async_connect with 0-RTT msquic does conn callback with StreamsAvailable event in the same code path of set conn param session ticket. This causes deadlock when callback handler wait for quicer async_connect to release the lock of c_ctx when it is called with an opened handle. solution is to either do set session ticket in open_connection Or set session ticket in async_connect without an opened handle callstack: at /home/ubuntu/repo/quic/msquic/src/core/stream_set.c:343 --- c_src/quicer_connection.c | 44 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index a8c95a6a..c0e851d5 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -507,6 +507,7 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_INTERNAL_ERROR); QuicerRegistrationCTX *r_ctx = NULL; HQUIC registration = NULL; + ERL_NIF_TERM options = argv[1]; if (argc == 0) { @@ -516,7 +517,6 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) else { assert(argc == 1); - ERL_NIF_TERM options = argv[1]; if (!parse_registration(env, options, &r_ctx)) { return ERROR_TUPLE_2(ATOM_QUIC_REGISTRATION); @@ -562,6 +562,12 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) goto exit; } + if (!IS_SAME_TERM(ATOM_OK, + (res = parse_conn_resume_ticket(env, options, c_ctx)))) + { + goto exit; + } + eHandle = enif_make_resource(env, c_ctx); return SUCCESS(eHandle); @@ -666,12 +672,10 @@ async_connect3(ErlNifEnv *env, } } - if (is_reuse_handle) - { - enif_mutex_lock(c_ctx->lock); - } - + { + enif_mutex_lock(c_ctx->lock); + } assert(c_ctx->owner); // allocate config_resource for client connection @@ -723,6 +727,12 @@ async_connect3(ErlNifEnv *env, res = ERROR_TUPLE_2(ATOM_CONN_OPEN_ERROR); goto Error; } + + if (!IS_SAME_TERM( + ATOM_OK, (res = parse_conn_resume_ticket(env, eoptions, c_ctx)))) + { + goto Error; + } } assert(c_ctx->is_closed); @@ -737,12 +747,6 @@ async_connect3(ErlNifEnv *env, goto Error; } - if (!IS_SAME_TERM(ATOM_OK, - (res = parse_conn_resume_ticket(env, eoptions, c_ctx)))) - { - goto Error; - } - if (!IS_SAME_TERM( ATOM_OK, (res = parse_conn_disable_1rtt_encryption(env, eoptions, c_ctx)))) @@ -789,10 +793,10 @@ async_connect3(ErlNifEnv *env, enif_monitor_process(NULL, c_ctx, &c_ctx->owner->Pid, &c_ctx->owner_mon); eHandle = enif_make_resource(env, c_ctx); - if(is_reuse_handle) - { - enif_mutex_unlock(c_ctx->lock); - } + if (is_reuse_handle) + { + enif_mutex_unlock(c_ctx->lock); + } return SUCCESS(eHandle); Error: @@ -842,10 +846,10 @@ async_connect3(ErlNifEnv *env, // Error exit, it must be closed or Handle is NULL assert(c_ctx->is_closed || NULL == c_ctx->Connection); - if(is_reuse_handle) - { - enif_mutex_unlock(c_ctx->lock); - } + if (is_reuse_handle) + { + enif_mutex_unlock(c_ctx->lock); + } return res; }