diff --git a/Makefile b/Makefile index 3c9b577a..570631d5 100644 --- a/Makefile +++ b/Makefile @@ -40,12 +40,24 @@ xref: eunit: $(REBAR) eunit -v -c --cover_export_name eunit +.PHONY: proper +proper: + $(REBAR) proper -n 1000 + +.PHONY: proper-cover +proper-cover: + mkdir -p coverage + QUICER_TEST_COVER=1 $(REBAR) as test proper -c -n 1000 + lcov -c --directory c_build/CMakeFiles/quicer_nif.dir/c_src/ \ + --exclude "${PWD}/msquic/src/inc/*" \ + --output-file ./coverage/proper-lcov.info + .PHONY: ct ct: QUICER_USE_SNK=1 $(REBAR) as test ct -v --readable=true .PHONY: cover -cover: eunit +cover: eunit proper-cover mkdir -p coverage QUICER_TEST_COVER=1 QUICER_USE_SNK=1 $(REBAR) as test ct --cover --cover_export_name=ct -v $(REBAR) as test cover -v @@ -55,7 +67,7 @@ cover: eunit .PHONY: cover-html cover-html: cover - genhtml -o coverage/ coverage/lcov.info + genhtml -o coverage/ coverage/lcov.info coverage/proper-lcov.info .PHONY: dialyzer dialyzer: diff --git a/c_src/quicer_config.c b/c_src/quicer_config.c index 837ce0ba..18638742 100644 --- a/c_src/quicer_config.c +++ b/c_src/quicer_config.c @@ -706,6 +706,7 @@ encode_parm_to_eterm(ErlNifEnv *env, } else if ((QUICER_PARAM_HANDLE_TYPE_STREAM == Type && (QUIC_PARAM_STREAM_ID == Param + || QUIC_PARAM_STREAM_PRIORITY == Param || QUIC_PARAM_STREAM_0RTT_LENGTH == Param || QUIC_PARAM_STREAM_IDEAL_SEND_BUFFER_SIZE == Param)) || (QUICER_PARAM_HANDLE_TYPE_CONN == Type @@ -759,6 +760,10 @@ encode_parm_to_eterm(ErlNifEnv *env, && QUICER_PARAM_HANDLE_TYPE_CONN == Type)) { ERL_NIF_TERM ebin; + if (QUIC_PARAM_CONN_CLOSE_REASON_PHRASE == Param && BufferLength > 1) + { + BufferLength -= 1; // remove \0 + } unsigned char *bin_data = enif_make_new_binary(env, BufferLength, &ebin); if (!bin_data) { @@ -795,7 +800,7 @@ getopt3(ErlNifEnv *env, ERL_NIF_TERM elevel = argv[2]; void *q_ctx; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); if (!enif_is_atom(env, eopt)) { @@ -905,7 +910,7 @@ setopt4(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) ERL_NIF_TERM evalue = argv[2]; ERL_NIF_TERM elevel = argv[3]; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); void *q_ctx = NULL; if (!enif_is_atom(env, eopt)) @@ -1254,9 +1259,10 @@ get_stream_opt(ErlNifEnv *env, void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); uint64_t BuffUint64 = 0; + uint16_t BuffUint16 = 0; if (!IS_SAME_TERM(ATOM_FALSE, elevel)) { @@ -1273,6 +1279,12 @@ get_stream_opt(ErlNifEnv *env, BufferLength = sizeof(uint64_t); Buffer = &BuffUint64; } + else if (ATOM_QUIC_PARAM_STREAM_PRIORITY == optname) + { + Param = QUIC_PARAM_STREAM_PRIORITY; + BufferLength = sizeof(uint16_t); + Buffer = &BuffUint16; + } else if (ATOM_QUIC_STREAM_OPTS_ACTIVE == optname) { switch (s_ctx->owner->active) @@ -1337,7 +1349,7 @@ set_stream_opt(ErlNifEnv *env, void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); uint16_t BuffUint16 = 0; @@ -1439,10 +1451,14 @@ get_connection_opt(ErlNifEnv *env, uint32_t BufferLength = 0; uint32_t Param = 0; uint32_t Value = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); if (!IS_SAME_TERM(ATOM_FALSE, elevel)) { + if (!c_ctx->config_resource) + { + goto Exit; + } res = get_level_param(env, c_ctx->Connection, c_ctx->config_resource->Configuration, @@ -1620,13 +1636,17 @@ set_connection_opt(ErlNifEnv *env, uint32_t BufferLength = 0; uint32_t Param = 0; uint32_t Value = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); QUIC_ADDR addr; uint8_t phrase[512] = { 0 }; ErlNifBinary ticket; if (!IS_SAME_TERM(ATOM_FALSE, elevel)) { + if (!c_ctx->config_resource) + { + goto Exit; + } res = set_level_param(env, c_ctx->Connection, c_ctx->config_resource->Configuration, @@ -1950,7 +1970,7 @@ get_listener_opt(ErlNifEnv *env, uint32_t Param = 0; QUIC_ADDR q_addr = { 0 }; QUIC_LISTENER_STATISTICS stats = { 65535, 65535, 65535 }; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); if (!l_ctx) { @@ -2037,7 +2057,7 @@ set_listener_opt(ErlNifEnv *env, void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); ErlNifBinary bin; if (!l_ctx) { @@ -2105,7 +2125,7 @@ get_tls_opt(ErlNifEnv *env, HQUIC Handle, ERL_NIF_TERM optname) void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); uint8_t alpn[255] = { 0 }; if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_TLS_HANDSHAKE_INFO)) @@ -2196,7 +2216,7 @@ get_global_opt(ErlNifEnv *env, HQUIC Handle, ERL_NIF_TERM optname) uint32_t BufferLength = 0; uint32_t Param = 0; uint32_t percent = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); QUIC_SETTINGS Settings = { 0 }; uint8_t githash[41] = { 0 }; // git hash 40 chars + \0 @@ -2314,7 +2334,7 @@ set_global_opt(ErlNifEnv *env, void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); uint32_t percent = 0; uint32_t lbmode = 0; QUIC_SETTINGS Settings = { 0 }; @@ -2395,7 +2415,7 @@ get_config_opt(ErlNifEnv *env, HQUIC Handle, ERL_NIF_TERM optname) void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONFIGURATION_SETTINGS)) { @@ -2435,7 +2455,7 @@ set_config_opt(ErlNifEnv *env, void *Buffer = NULL; uint32_t BufferLength = 0; uint32_t Param = 0; - ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + ERL_NIF_TERM res = ERROR_TUPLE_2(ATOM_ERROR_NOT_FOUND); QUIC_SETTINGS Settings = { 0 }; if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONFIGURATION_SETTINGS)) diff --git a/c_src/quicer_nif.c b/c_src/quicer_nif.c index 92fa47e6..8b10a472 100644 --- a/c_src/quicer_nif.c +++ b/c_src/quicer_nif.c @@ -524,64 +524,56 @@ ERL_NIF_TERM ATOM_QUIC_DATAGRAM_SEND_CANCELED; \ /* Parameters for QUIC_PARAM_LEVEL_GLOBAL. */ \ ATOM(ATOM_QUIC_GLOBAL, quic_global); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_RETRY_MEMORY_PERCENT, \ - param_global_retry_memory_percent); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_SUPPORTED_VERSIONS, \ - param_global_supported_versions); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, \ - param_global_load_balacing_mode); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_PERF_COUNTERS, param_global_perf_counters); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_SETTINGS, param_global_settings); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_VERSION, param_global_version); \ - ATOM(ATOM_QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH, \ - param_global_library_git_hash); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_RETRY_MEMORY_PERCENT, retry_memory_percent); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_SUPPORTED_VERSIONS, supported_versions); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, load_balacing_mode); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_PERF_COUNTERS, perf_counters); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_SETTINGS, settings); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_VERSION, version); \ + ATOM(ATOM_QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH, library_git_hash); \ \ /*Parameters for QUIC_PARAM_LEVEL_REGISTRATION.*/ \ ATOM(ATOM_QUIC_REGISTRATION, quic_registration); \ - ATOM(ATOM_QUIC_PARAM_REGISTRATION_CID_PREFIX, \ - param_registration_cid_prefix); \ + ATOM(ATOM_QUIC_PARAM_REGISTRATION_CID_PREFIX, cid_prefix); \ \ /* Parameters for QUIC_PARAM_LEVEL_CONFIGURATION. */ \ ATOM(ATOM_QUIC_CONFIGURATION, quic_configuration); \ - ATOM(ATOM_QUIC_PARAM_CONFIGURATION_SETTINGS, param_configuration_settings); \ + ATOM(ATOM_QUIC_PARAM_CONFIGURATION_SETTINGS, settings); \ \ /* Parameters for QUIC_PARAM_LEVEL_LISTENER. */ \ \ - ATOM(ATOM_QUIC_PARAM_LISTENER_LOCAL_ADDRESS, param_listener_local_address); \ - ATOM(ATOM_QUIC_PARAM_LISTENER_STATS, param_listener_stats); \ - ATOM(ATOM_QUIC_PARAM_LISTENER_CIBIR_ID, param_listener_cibir_id); \ + ATOM(ATOM_QUIC_PARAM_LISTENER_LOCAL_ADDRESS, local_address); \ + ATOM(ATOM_QUIC_PARAM_LISTENER_STATS, stats); \ + ATOM(ATOM_QUIC_PARAM_LISTENER_CIBIR_ID, cibir_id); \ \ /* Parameters for QUIC_PARAM_LEVEL_CONNECTION. */ \ \ - ATOM(ATOM_QUIC_PARAM_CONN_QUIC_VERSION, param_conn_quic_version); \ - ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, param_conn_local_address); \ - ATOM(ATOM_QUIC_PARAM_CONN_REMOTE_ADDRESS, param_conn_remote_address); \ - ATOM(ATOM_QUIC_PARAM_CONN_IDEAL_PROCESSOR, param_conn_ideal_processor); \ - ATOM(ATOM_QUIC_PARAM_CONN_SETTINGS, param_conn_settings); \ - ATOM(ATOM_QUIC_PARAM_CONN_STATISTICS, param_conn_statistics); \ - ATOM(ATOM_QUIC_PARAM_CONN_STATISTICS_PLAT, param_conn_statistics_plat); \ - ATOM(ATOM_QUIC_PARAM_CONN_SHARE_UDP_BINDING, param_conn_share_udp_binding); \ + ATOM(ATOM_QUIC_PARAM_CONN_QUIC_VERSION, quic_version); \ + ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, local_address); \ + ATOM(ATOM_QUIC_PARAM_CONN_REMOTE_ADDRESS, remote_address); \ + ATOM(ATOM_QUIC_PARAM_CONN_IDEAL_PROCESSOR, ideal_processor); \ + ATOM(ATOM_QUIC_PARAM_CONN_SETTINGS, settings); \ + ATOM(ATOM_QUIC_PARAM_CONN_STATISTICS, statistics); \ + ATOM(ATOM_QUIC_PARAM_CONN_STATISTICS_PLAT, statistics_plat); \ + ATOM(ATOM_QUIC_PARAM_CONN_SHARE_UDP_BINDING, share_udp_binding); \ ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT, \ - param_conn_local_bidi_stream_count); \ + local_bidi_stream_count); \ ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT, \ - param_conn_local_unidi_stream_count); \ - ATOM(ATOM_QUIC_PARAM_CONN_MAX_STREAM_IDS, param_conn_max_stream_ids); \ - ATOM(ATOM_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE, \ - param_conn_close_reason_phrase); \ + local_unidi_stream_count); \ + ATOM(ATOM_QUIC_PARAM_CONN_MAX_STREAM_IDS, max_stream_ids); \ + ATOM(ATOM_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE, close_reason_phrase); \ ATOM(ATOM_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME, \ - param_conn_stream_scheduling_scheme); \ + stream_scheduling_scheme); \ ATOM(ATOM_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED, \ - param_conn_datagram_receive_enabled); \ - ATOM(ATOM_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED, \ - param_conn_datagram_send_enabled); \ + datagram_receive_enabled); \ + ATOM(ATOM_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED, datagram_send_enabled); \ \ ATOM(ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, \ - param_conn_disable_1rtt_encryption); \ + disable_1rtt_encryption); \ \ - ATOM(ATOM_QUIC_PARAM_CONN_RESUMPTION_TICKET, param_conn_resumption_ticket); \ - ATOM(ATOM_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID, \ - param_conn_peer_certificate_valid); \ - ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_INTERFACE, param_conn_local_interface); \ + ATOM(ATOM_QUIC_PARAM_CONN_RESUMPTION_TICKET, resumption_ticket); \ + ATOM(ATOM_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID, peer_certificate_valid); \ + ATOM(ATOM_QUIC_PARAM_CONN_LOCAL_INTERFACE, local_interface); \ /* Parameters for QUIC_PARAM_LEVEL_TLS. */ \ ATOM(ATOM_QUIC_TLS, quic_tls); \ ATOM(ATOM_TLS_PROTOCOL_VERSION, tls_protocol_version); \ @@ -605,19 +597,19 @@ ERL_NIF_TERM ATOM_QUIC_DATAGRAM_SEND_CANCELED; ATOM(ATOM_AES_256_GCM_SHA384, aes_256_gcm_sha384); \ ATOM(ATOM_CHACHA20_POLY1305_SHA256, chacha20_poly1305_sha256); \ ATOM(ATOM_QUIC_PARAM_TLS_SCHANNEL_CONTEXT_ATTRIBUTE_W, \ - param_tls_schannel_context_attribute_w); \ + schannel_context_attribute_w); \ \ - ATOM(ATOM_QUIC_PARAM_TLS_HANDSHAKE_INFO, param_tls_handshake_info); \ + ATOM(ATOM_QUIC_PARAM_TLS_HANDSHAKE_INFO, handshake_info); \ \ - ATOM(ATOM_QUIC_PARAM_TLS_NEGOTIATED_ALPN, param_tls_negotiated_alpn); \ + ATOM(ATOM_QUIC_PARAM_TLS_NEGOTIATED_ALPN, negotiated_alpn); \ \ /* Parameters for QUIC_PARAM_LEVEL_STREAM. */ \ \ - ATOM(ATOM_QUIC_PARAM_STREAM_ID, param_stream_id); \ - ATOM(ATOM_QUIC_PARAM_STREAM_0RTT_LENGTH, param_stream_0rtt_length); \ + ATOM(ATOM_QUIC_PARAM_STREAM_ID, stream_id); \ + ATOM(ATOM_QUIC_PARAM_STREAM_0RTT_LENGTH, 0rtt_length); \ ATOM(ATOM_QUIC_PARAM_STREAM_IDEAL_SEND_BUFFER_SIZE, \ - param_stream_ideal_send_buffer_size); \ - ATOM(ATOM_QUIC_PARAM_STREAM_PRIORITY, param_stream_priority); \ + ideal_send_buffer_size); \ + ATOM(ATOM_QUIC_PARAM_STREAM_PRIORITY, priority); \ \ /*-----------------------*/ \ /* msquic params ends */ \ @@ -1391,16 +1383,26 @@ controlling_process(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if (enif_get_resource(env, argv[0], ctx_stream_t, (void **)&s_ctx)) { + if (!get_stream_handle(s_ctx)) + { + return ERROR_TUPLE_2(ATOM_CLOSED); + } + enif_mutex_lock(s_ctx->lock); res = stream_controlling_process(env, s_ctx, &caller, &new_owner); enif_mutex_unlock(s_ctx->lock); + put_stream_handle(s_ctx); } else if (enif_get_resource(env, argv[0], ctx_connection_t, (void **)&c_ctx)) { - + if (!get_conn_handle(c_ctx)) + { + return ERROR_TUPLE_2(ATOM_CLOSED); + } enif_mutex_lock(c_ctx->lock); res = connection_controlling_process(env, c_ctx, &caller, &new_owner); enif_mutex_unlock(c_ctx->lock); + put_conn_handle(c_ctx); } else { diff --git a/c_src/quicer_reg.c b/c_src/quicer_reg.c index 231fcafd..13ba104a 100644 --- a/c_src/quicer_reg.c +++ b/c_src/quicer_reg.c @@ -113,7 +113,7 @@ deregistration(__unused_parm__ ErlNifEnv *env, ERL_NIF_TERM new_registration2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - + CXPLAT_FRE_ASSERT(argc >= 1); ERL_NIF_TERM ename = argv[0]; ERL_NIF_TERM eprofile = argv[1]; QUIC_REGISTRATION_CONFIG RegConfig @@ -134,8 +134,9 @@ new_registration2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } if (argc == 2 - && 0 >= enif_get_string( - env, ename, r_ctx->name, UINT8_MAX + 1, ERL_NIF_LATIN1)) + && (0 >= enif_get_string( + env, ename, r_ctx->name, UINT8_MAX + 1, ERL_NIF_LATIN1) + || strlen(r_ctx->name) == 0)) { res = ERROR_TUPLE_2(ATOM_BADARG); goto exit; diff --git a/c_src/quicer_stream.c b/c_src/quicer_stream.c index cab3fc5a..0acbadcf 100644 --- a/c_src/quicer_stream.c +++ b/c_src/quicer_stream.c @@ -475,7 +475,7 @@ csend4(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { if (!enif_get_uint(env, eopen_flag, &open_flag)) { - // if set must be valid. + // if set, must be valid. return ERROR_TUPLE_2(ATOM_BADARG); } } diff --git a/include/quicer_types.hrl b/include/quicer_types.hrl index 0ccec19f..b1a6fdf3 100644 --- a/include/quicer_types.hrl +++ b/include/quicer_types.hrl @@ -92,6 +92,7 @@ }. -type uint64() :: 0..?MASK(64). +-type uint62() :: 0..?MASK(62). -type uint32() :: 0..?MASK(32). -type uint16() :: 0..?MASK(16). -type uint8() :: 0..?MASK(8). @@ -152,13 +153,13 @@ nst => binary(), cacertfile => file:filename(), sslkeylogfile => file:filename(), - peer_bidi_stream_count => uint16(), - peer_unidi_stream_count => uint16(), handshake_idle_timeout_ms => non_neg_integer(), quic_event_mask => uint32(), - param_conn_disable_1rtt_encryption => boolean(), + disable_1rtt_encryption => boolean(), %% Not working well - param_conn_local_address => string(), + local_address => string(), + local_bidi_stream_count => uint16(), + local_peer_unidi_stream_count => uint16(), %% for Application defined options _ => _ }. @@ -168,12 +169,18 @@ %% @TODO expand -type acceptor_opts() :: map(). +-type active_n() :: boolean() | once | integer(). + -type stream_opts() :: #{ - active := boolean() | once | integer(), + active := active_n(), open_flag => stream_open_flags(), start_flag => stream_start_flags(), event_mask => uint32(), disable_fpbuffer => boolean(), + stream_id => uint62(), + priority => uint16(), + ideal_send_buffer_size => uint64(), + '0rtt_length' => uint64(), %% for Application defined options _ => _ %% @TODO expand @@ -211,7 +218,23 @@ | ?QUIC_STREAM_SHUTDOWN_FLAG_IMMEDIATE. %% is sync send or not --type send_flags() :: non_neg_integer(). +-type send_flags() :: + csend_flags() | ?QUICER_SEND_FLAG_SYNC. + +-type csend_flags() :: + ?QUIC_SEND_FLAG_NONE + | ?QUIC_SEND_FLAG_ALLOW_0_RTT + | ?QUIC_SEND_FLAG_START + | ?QUIC_SEND_FLAG_FIN + | ?QUIC_SEND_FLAG_DGRAM_PRIORITY + | ?QUIC_SEND_FLAG_DELAY_SEND. + +-type stream_start_flag() :: + ?QUIC_STREAM_START_FLAG_NONE + | ?QUIC_STREAM_START_FLAG_IMMEDIATE + | ?QUIC_STREAM_START_FLAG_FAIL_BLOCKED + | ?QUIC_STREAM_START_FLAG_SHUTDOWN_ON_FAIL + | ?QUIC_STREAM_START_FLAG_INDICATE_PEER_ACCEPT. -type new_stream_props() :: #{ is_orphan := boolean(), @@ -250,52 +273,52 @@ -type optname_conn() :: %% /* Parameters for QUIC_PARAM_LEVEL_CONNECTION. */| %% | X | | - param_conn_quic_version + quic_version %% | X | | - | param_conn_local_address + | local_address %% | X | X | @TODO SET - | param_conn_remote_address + | remote_address %% | X | | - | param_conn_ideal_processor + | ideal_processor %% | X | X | - | param_conn_settings + | settings %% | X | | - | param_conn_statistics + | statistics %% | X | | @TODO - | param_conn_statistics_plat + | statistics_plat %% | X | X | - | param_conn_share_udp_binding + | share_udp_binding %% | X | | - | param_conn_local_bidi_stream_count + | local_bidi_stream_count %% | X | | - | param_conn_local_unidi_stream_count + | local_unidi_stream_count %% | X | | - | param_conn_max_stream_ids + | max_stream_ids %% | X | X | - | param_conn_close_reason_phrase + | close_reason_phrase %% | X | X | - | param_conn_stream_scheduling_scheme + | stream_scheduling_scheme %% | X | X | - | param_conn_datagram_receive_enabled + | datagram_receive_enabled %% | X | | - | param_conn_datagram_send_enabled + | datagram_send_enabled %% | X | X | - | param_conn_disable_1rtt_encryption + | disable_1rtt_encryption %% | | X | - | param_conn_resumption_ticket + | resumption_ticket %% | | X | - | param_conn_peer_certificate_valid + | peer_certificate_valid %% | | X | - | param_conn_local_interface. + | local_interface. %% with connection_handle() -type optname_tls() :: %% | X | | - param_tls_schannel_context_attribute_w + schannel_context_attribute_w %% | X | | - | param_tls_handshake_info + | handshake_info %% | X | | - | param_tls_negotiated_alpn. + | negotiated_alpn. -type optname_stream() :: %% | X | X | @@ -303,47 +326,47 @@ %% | | X | | controlling_process %% | | X | - | param_stream_id + | stream_id %% | X | | - | param_stream_0rtt_length + | '0rtt_length' %% | X | | - | param_stream_ideal_send_buffer_size + | ideal_send_buffer_size %% | | | - | param_stream_priority. + | priority. %% with `undefined' handle -type optname_global() :: %% | X | X | - param_global_retry_memory_percent + retry_memory_percent %% | X | | @TODO - | param_global_supported_versions + | supported_versions %% | X | X | - | param_global_load_balacing_mode + | load_balacing_mode %% | X | | - | param_global_perf_counters + | perf_counters %% | X | X | - | param_global_settings + | global_settings %% | X | | @TODO - | param_global_version. + | global_version. %% | X | X | @TODO --type optname_reg() :: param_registration_cid_prefix. +-type optname_reg() :: cid_prefix. %% with config_handle() -type optname_configuration() :: %% | X | X | - param_configuration_settings + settings %% | | X | @TODO - | param_configuration_ticket_keys. + | ticket_keys. %% with listener_handle -type optname_listener() :: %% | X | | - param_listener_local_address + local_address %% | X | | - | param_listener_stats + | stats %% | | X | - | param_listener_cibir_id. + | cibir_id. -type conn_settings() :: [{conn_settings_key(), non_neg_integer()}]. -type conn_settings_key() :: diff --git a/rebar.config b/rebar.config index 2591d4ca..b3d2d11f 100644 --- a/rebar.config +++ b/rebar.config @@ -7,7 +7,10 @@ {profiles, [ {test, [ {erl_opts, [{d, 'SNK_COLLECTOR'}]}, - {src_dirs, ["src", "test/example"]} + {src_dirs, ["src", "test/example"]}, + {deps, [ + {proper, {git, "https://github.com/proper-testing/proper.git", {branch, "master"}}} + ]} ]}, {doc, [ {plugins, [rebar3_hex, rebar3_ex_doc]}, @@ -36,7 +39,8 @@ {coveralls, {git, "https://github.com/qzhuyan/coveralls-erl", {branch, "qzhuyan"}}}, rebar3_hex, rebar3_ex_do, - erlfmt + erlfmt, + rebar3_proper ]}. %% Coveralls diff --git a/src/quicer.erl b/src/quicer.erl index 9efdfc41..e161450e 100644 --- a/src/quicer.erl +++ b/src/quicer.erl @@ -168,7 +168,10 @@ %% Suporting types error_code/0, - quicer_addr/0 + quicer_addr/0, + + %% Registraion Profiles + registration_profile/0 ]). -type connection_opts() :: proplists:proplist() | conn_opts(). @@ -956,7 +959,7 @@ getopt(Handle, Opt) -> -spec getopt(handle(), optname(), optlevel()) -> %% `optname' not found, or wrong `optlevel' must be a bug. not_found - %% when optname = param_conn_settings + %% when optname = settings | {ok, [any()]} | {error, badarg | param_error | internal_error | not_enough_mem} | {error, atom_reason()}. @@ -969,8 +972,8 @@ getopt(Handle, Opt, Optlevel) -> ok | {error, badarg | param_error | internal_error | not_enough_mem} | {error, atom_reason()}. -setopt(Handle, param_conn_settings, Value) when is_list(Value) -> - setopt(Handle, param_conn_settings, maps:from_list(Value)); +setopt(Handle, settings, Value) when is_list(Value) -> + setopt(Handle, settings, maps:from_list(Value)); setopt({_Conn, Stream}, active, Value) -> setopt(Stream, active, Value); setopt(Handle, Opt, Value) -> @@ -987,14 +990,14 @@ setopt(Handle, Opt, Value, Level) -> -spec get_stream_id(Stream :: stream_handle()) -> {ok, integer()} | {error, any()} | not_found. get_stream_id(Stream) -> - quicer_nif:getopt(Stream, param_stream_id, false). + quicer_nif:getopt(Stream, stream_id, false). %% @doc get connection state %% mimic {@link ssl:getstat/2} -spec getstat(connection_handle(), [inet:stat_option()]) -> {ok, list()} | {error, any()}. getstat(Conn, Cnts) -> - case quicer_nif:getopt(Conn, param_conn_statistics, false) of + case quicer_nif:getopt(Conn, statistics, false) of {error, _} = E -> E; {ok, Res} -> @@ -1013,14 +1016,14 @@ getstat(Conn, Cnts) -> -spec negotiated_protocol(Conn :: connection_handle()) -> {ok, Protocol :: binary()} | {error, Reason :: any()}. negotiated_protocol(Conn) -> - quicer:getopt(Conn, param_tls_negotiated_alpn, quic_tls). + quicer:getopt(Conn, negotiated_alpn, quic_tls). %% @doc Peer name %% mimic {@link ssl:peername/1} -spec peername(connection_handle() | stream_handle()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, any()}. peername(Handle) -> - quicer_nif:getopt(Handle, param_conn_remote_address, false). + quicer_nif:getopt(Handle, remote_address, false). %% @doc Peer Cert in DER-encoded binary %% mimic {@link ssl:peername/1} @@ -1200,7 +1203,7 @@ perf_counters() -> case quicer_nif:getopt( quic_global, - param_global_perf_counters, + perf_counters, false ) of diff --git a/src/quicer_nif.erl b/src/quicer_nif.erl index d2811356..2b03d3f7 100644 --- a/src/quicer_nif.erl +++ b/src/quicer_nif.erl @@ -78,7 +78,9 @@ get_registration_name/0, get_listeners/0, get_connections/0, - get_owner/0 + get_owner/0, + + reg_handle/0 ]). %% NIF fuction return types @@ -302,12 +304,12 @@ sockname(_Conn) -> -spec getopt(handle(), optname(), optlevel()) -> %% `optname' not found, or wrong `optlevel' must be a bug. not_found - %% when optname = param_conn_settings + %% when optname = settings | {ok, any()} | {error, badarg | param_error | internal_error | not_enough_mem} | {error, atom_reason()}. -getopt(_Handle, _Optname, _IsRaw) -> +getopt(_Handle, _Optname, _Level) -> erlang:nif_error(nif_library_not_loaded). -spec setopt(handle(), optname(), any(), optlevel()) -> diff --git a/src/quicer_server_conn_callback.erl b/src/quicer_server_conn_callback.erl index 7040b8a5..d2edc78b 100644 --- a/src/quicer_server_conn_callback.erl +++ b/src/quicer_server_conn_callback.erl @@ -94,7 +94,7 @@ new_stream( ok -> {ok, CBState#{streams := [{StreamOwner, Stream} | Streams]}}; {error, _} = E -> - E + {stop, {shutdown, {handoff, E}, CBState}} end; Other -> Other diff --git a/test/example_client_connection.erl b/test/example_client_connection.erl index 9dfc77bc..131f4a09 100644 --- a/test/example_client_connection.erl +++ b/test/example_client_connection.erl @@ -135,12 +135,12 @@ streams_available(_C, {_BidirCnt, _UnidirCnt}, S) -> {hibernate, S}. peer_needs_streams(C, unidi_streams, S) -> - {ok, Current} = quicer:getopt(C, param_conn_local_unidi_stream_count), - ok = quicer:setopt(C, param_conn_settings, #{peer_unidi_stream_count => Current + 1}), + {ok, Current} = quicer:getopt(C, local_unidi_stream_count), + ok = quicer:setopt(C, settings, #{peer_unidi_stream_count => Current + 1}), {ok, S}; peer_needs_streams(C, bidi_streams, S) -> - {ok, Current} = quicer:getopt(C, param_conn_local_bidi_stream_count), - ok = quicer:setopt(C, param_conn_settings, #{peer_bidi_stream_count => Current + 1}), + {ok, Current} = quicer:getopt(C, local_bidi_stream_count), + ok = quicer:setopt(C, settings, #{peer_bidi_stream_count => Current + 1}), {ok, S}. handle_info({'EXIT', _Pid, _Reason}, State) -> diff --git a/test/example_server_connection.erl b/test/example_server_connection.erl index 1db8c40d..485eda4d 100644 --- a/test/example_server_connection.erl +++ b/test/example_server_connection.erl @@ -72,7 +72,7 @@ new_conn(Conn, #{version := _Vsn}, #{stream_opts := SOpts} = S) -> streams => [{Pid, undefined}] }}; {error, _} = Error -> - Error + {stop, Error, S#{conn => Conn}} end. connected(_Conn, _Flags, S) -> @@ -105,6 +105,8 @@ new_stream( case quicer:handoff_stream(Stream, StreamOwner) of ok -> {ok, CBState#{streams := [{StreamOwner, Stream} | Streams]}}; + {error, closed} -> + {stop, {shutdown, closed}, CBState}; false -> {error, handoff_fail} end; @@ -128,8 +130,8 @@ streams_available(_C, {_BidirCnt, _UnidirCnt}, S) -> {ok, S}. peer_needs_streams(C, unidi_streams, S) -> - {ok, Current} = quicer:getopt(C, param_conn_local_unidi_stream_count), - ok = quicer:setopt(C, param_conn_settings, #{peer_unidi_stream_count => Current + 1}), + {ok, Current} = quicer:getopt(C, local_unidi_stream_count), + ok = quicer:setopt(C, settings, #{peer_unidi_stream_count => Current + 1}), {ok, S}; peer_needs_streams(_C, bidi_streams, S) -> %% leave it for test case to unblock it, see tc_multi_streams_example_server_3 diff --git a/test/example_server_stream.erl b/test/example_server_stream.erl index 041c65b2..6035b13b 100644 --- a/test/example_server_stream.erl +++ b/test/example_server_stream.erl @@ -39,7 +39,7 @@ -include("quicer.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). -init_handoff(Stream, StreamOpts, Conn, #{flags := Flags}) -> +init_handoff(Stream, _StreamOpts, Conn, #{flags := Flags}) -> InitState = #{ stream => Stream, conn => Conn, @@ -47,7 +47,7 @@ init_handoff(Stream, StreamOpts, Conn, #{flags := Flags}) -> is_local => false, is_unidir => quicer:is_unidirectional(Flags) }, - ct:pal("init_handoff ~p", [{InitState, StreamOpts}]), + % ct:pal("init_handoff ~p", [{InitState, _StreamOpts}]), {ok, InitState}. post_handoff(Stream, _PostData, State) -> @@ -111,14 +111,18 @@ handle_stream_data( Stream, <<"flow_control.enable_bidi">> = Bin, _Flags, #{is_unidir := true, conn := Conn} = State ) -> ?tp(debug, #{stream => Stream, data => Bin, module => ?MODULE, dir => unidir}), - ok = quicer:setopt(Conn, param_conn_settings, #{peer_bidi_stream_count => 2}), + ok = quicer:setopt(Conn, settings, #{peer_bidi_stream_count => 2}), {ok, State}; handle_stream_data(Stream, Bin, _Flags, #{is_unidir := false} = State) -> %% for bidir stream, we just echo in place. ?tp(debug, #{stream => Stream, data => Bin, module => ?MODULE, dir => bidir}), ct:pal("Server recv: ~p from ~p", [Bin, Stream]), - {ok, _} = quicer:send(Stream, Bin), - {ok, State}; + case quicer:send(Stream, Bin) of + {ok, _} -> + {ok, State}; + {error, closed} -> + {stop, {shutdown, closed}, State} + end; handle_stream_data( Stream, Bin, _Flags, #{is_unidir := true, peer_stream := PeerStream, conn := Conn} = State ) -> diff --git a/test/prop_quic_types.hrl b/test/prop_quic_types.hrl new file mode 100644 index 00000000..abbb6560 --- /dev/null +++ b/test/prop_quic_types.hrl @@ -0,0 +1,155 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 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. +%%-------------------------------------------------------------------- + +-record(prop_handle, { + type :: reg | listen | conn | stream, + name :: string(), + handle :: reference(), + destructor :: fun() +}). + +-define(dummy_listener, dummy_listener). +-define(DUMMY_PORT, 14567). + +-define(valid_flags(FlagType), + (?SUCHTHAT( + Flag, + ?LET( + Flags, + [FlagType], + begin + lists:foldl( + fun(F, Acc) -> + Acc bor F + end, + 0, + Flags + ) + end + ), + Flag =/= 0 + )) +). + +-type quicer_listen_opts() :: [listen_opt()]. + +-type listen_opt() :: + {alpn, [alpn()]} + | {cert, file:filename()} + | {certfile, file:filename()} + %-| {key, file:filename()}. %% @FIXME reflect in types + | {keyfile, file:filename()} + | {verify, none | peer | verify_peer | verify_none} + | {cacertfile, file:filename()} + | {password, string()} + | {sslkeylogfile, file:filename()} + | {allow_insecure, boolean()} + %-| {quic_registration, reg_handle()} + | {conn_acceptors, non_neg_integer()} + | {settings, [quicer_setting()]}. + +-type quicer_setting() :: + {max_bytes_per_key, uint64()} + | {handshake_idle_timeout_ms, uint64()} + | {idle_timeout_ms, uint64()} + | {tls_client_max_send_buffer, uint32()} + | {tls_server_max_send_buffer, uint32()} + | {stream_recv_window_default, uint32()} + | {stream_recv_buffer_default, uint32()} + | {conn_flow_control_window, uint32()} + | {max_stateless_operations, uint32()} + | {initial_window_packets, uint32()} + | {send_idle_timeout_ms, uint32()} + | {initial_rtt_ms, uint32()} + | {max_ack_delay_ms, uint32()} + | {disconnect_timeout_ms, uint32()} + | {keep_alive_interval_ms, uint32()} + | {peer_bidi_stream_count, uint16()} + | {peer_unidi_stream_count, uint16()} + | {retry_memory_limit, uint16()} + | {load_balancing_mode, uint16()} + | {max_operations_per_drain, uint8()} + | {send_buffering_enabled, uint8()} + | {pacing_enabled, uint8()} + | {migration_enabled, uint8()} + | {datagram_receive_enabled, uint8()} + | {server_resumption_level, 0 | 1 | 2} + | {minimum_mtu, uint16()} + | {maximum_mtu, uint16()} + | {mtu_discovery_search_complete_timeout_us, uint64()} + | {mtu_discovery_missing_probe_count, uint8()} + | {max_binding_stateless_operations, uint16()} + | {stateless_operation_expiration_ms, uint16()}. + +-type quicer_conn_opts() :: [conn_opt()]. +-type conn_opt() :: + {alpn, [string()]} + | {certfile, file:filename()} + | {keyfile, file:filename()} + | {password, string()} + | {verify, none | peer} + | {nst, binary()} + | {cacertfile, file:filename()} + | {sslkeylogfile, file:filename()} + | {local_bidi_stream_count, uint16()} + | {local_unidi_stream_count, uint16()} + | {handshake_idle_timeout_ms, non_neg_integer()} + | {quic_event_mask, uint32()} + | {disable_1rtt_encryption, boolean()} + | {quic_version, uint32()} + | {local_address, string()} + | {remote_address, string()} + | {ideal_processor, uint16()} + | {settings, [quicer_setting()]} + % @TODO + | {statistics, any()} + % @TODO + | {statistics_plat, any()} + | {share_udp_binding, boolean()} + | {max_stream_ids, uint64()} + | {close_reason_phrase, string()} + | {stream_scheduling_scheme, uint16()} + | {datagram_receive_enabled, boolean()} + | {datagram_send_enabled, boolean()} + | {resumption_ticket, [uint8()]} + | {peer_certificate_valid, boolean()} + | {local_interface, uint32()} + % @TODO + | {tls_secrets, binary()} + % @TODO + | {version_settings, any()} + | {cibir_id, [uint8()]} + % @TODO + | {statistics_v2, any()} + % @TODO + | {statistics_v2_plat, any()}. + +-type quicer_acceptor_opts() :: [acceptor_opt()]. +-type acceptor_opt() :: + {active, active_n()} + | quicer_setting(). + +-type quicer_stream_opts() :: [stream_opt()]. +-type stream_opt() :: + {active, active_n()} + | {stream_id, uint62()} + | {priority, uint16()} + | {ideal_send_buffer_size, uint64()} + | {'0rtt_length', uint64()} + | {open_flag, stream_open_flags()} + | {start_flag, stream_start_flags()} + | {event_mask, uint32()} + | {disable_fpbuffer, boolean()}. diff --git a/test/prop_quicer_nif.erl b/test/prop_quicer_nif.erl new file mode 100644 index 00000000..94fbe883 --- /dev/null +++ b/test/prop_quicer_nif.erl @@ -0,0 +1,736 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 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(prop_quicer_nif). +-include_lib("proper/include/proper.hrl"). +-include_lib("quicer/include/quicer_types.hrl"). +-include("prop_quic_types.hrl"). + +-import( + quicer_prop_gen, + [ + valid_client_conn_opts/0, + valid_server_listen_opts/0, + valid_stream_shutdown_flags/0, + valid_stream_start_flags/0, + valid_stream_handle/0, + valid_started_connection_handle/0, + valid_opened_connection_handle/0, + valid_connection_handle/0, + valid_listen_on/0, + valid_listen_opts/0, + valid_listen_handle/0, + valid_global_handle/0, + valid_reg_handle/0, + valid_handle/0, + pid/0, + data/0, + quicer_send_flags/0 + ] +). + +prop_robust_new_registration_2() -> + ?FORALL( + {Key, Value}, + {string(), term()}, + begin + case quicer_nif:new_registration(Key, Value) of + {ok, _} -> + true; + {error, _} -> + true + end + end + ). + +prop_shutdown_registration_1() -> + ?FORALL( + #prop_handle{type = reg, handle = Handle}, + valid_reg_handle(), + begin + ok == quicer_nif:shutdown_registration(Handle) + end + ). + +prop_shutdown_registration_3() -> + ?FORALL( + {#prop_handle{type = reg, handle = Handle}, IsSilent, ErrorCode}, + {valid_reg_handle(), boolean(), uint64()}, + begin + ok == quicer_nif:shutdown_registration(Handle, IsSilent, ErrorCode) + end + ). + +prop_close_registration_1() -> + ?FORALL( + #prop_handle{type = reg, handle = Handle}, + valid_reg_handle(), + begin + ok == quicer_nif:close_registration(Handle) + end + ). + +prop_get_registration_name() -> + ?FORALL( + #prop_handle{type = reg, name = Name, handle = Handle} = H, + valid_reg_handle(), + begin + Res = quicer_nif:get_registration_name(Handle), + (H#prop_handle.destructor)(), + {ok, Name} == Res + end + ). + +%% robustness test, no crash +prop_listen_robust() -> + ?FORALL( + {On, Opts}, + {listen_on(), quicer_listen_opts()}, + begin + case quicer_nif:listen(On, maps:from_list(Opts)) of + {ok, Handle} -> + quicer_nif:close_listener(Handle), + true; + {error, _} -> + true; + {error, _, _} -> + true + end + end + ). + +%% robustness test, no crash +%% precondition: with valid listener handle +prop_start_listener_with_valid_handle() -> + ?FORALL( + {#prop_handle{type = listener, handle = Handle, destructor = Destroy}, On, Opts}, + {valid_listen_handle(), listen_on(), quicer_listen_opts()}, + begin + case quicer_nif:start_listener(Handle, On, maps:from_list(Opts)) of + {ok, _} -> + Destroy(), + true; + {error, _} -> + Destroy(), + true + end + end + ). + +%% robustness test, no crash +prop_robust_stop_listener() -> + ?FORALL( + Handle, + any(), + begin + collect(quicer_nif:stop_listener(Handle), true) + end + ). + +%% robustness test, no crash +prop_robust_close_listener() -> + ?FORALL( + Handle, + any(), + begin + collect(quicer_nif:close_listener(Handle), true) + end + ). + +%% stop_listener with valid listen handle must success +prop_stop_listener_with_valid_handle() -> + ?FORALL( + #prop_handle{type = listener, handle = Handle}, + valid_listen_handle(), + begin + ok == quicer_nif:stop_listener(Handle) + end + ). + +%% @doc Start stopped Listener must success with valid opts +%% precondition: with valid listener handle AND valid listen on AND valid listen TLS opts +prop_start_listener_with_valid_handle_AND_valid_listen_on() -> + ?FORALL( + {#prop_handle{type = listener, handle = Handle, destructor = Destroy}, On, Opts}, + {valid_listen_handle(), valid_listen_on(), valid_listen_opts()}, + begin + ok = quicer_nif:stop_listener(Handle), + LOpts = maps:from_list(Opts), + Res = quicer_nif:start_listener(Handle, On, LOpts), + Destroy(), + % collect(Res, Res == ok orelse Res == {error, invalid_parameter}) + collect(Res, true) + end + ). + +%% robustness test, no crash +prop_robust_open_connection_0() -> + ?FORALL( + _, + integer(), + begin + {ok, H} = quicer_nif:open_connection(), + quicer:async_shutdown_connection(H, 0, 0), + true + end + ). + +%% robustness test, no crash +prop_robust_open_connection_1() -> + ?FORALL( + #prop_handle{type = reg, handle = Handle, destructor = Destroy}, + valid_reg_handle(), + begin + {ok, _Handle} = quicer_nif:open_connection(Handle), + quicer_nif:async_shutdown_connection(Handle, 0, 0), + Destroy(), + true + end + ). + +%% robustness test, no crash +prop_robust_async_connect_3() -> + Port = quicer_test_lib:select_free_port(quic), + {ok, LH} = quicer_nif:listen(Port, maps:from_list(valid_server_listen_opts())), + ?FORALL( + ConnOpts, + quicer_conn_opts(), + begin + COpts = maps:from_list(ConnOpts), + case quicer_nif:async_connect("localhost", Port, COpts) of + {ok, ConnHandle} -> + quicer:close_listener(LH), + quicer_nif:async_shutdown_connection(ConnHandle, 0, 0), + collect(ok, true); + E -> + quicer:close_listener(LH), + collect(E, true) + end + end + ). + +%% precondition: with valid TLS opts +prop_async_connect_3_with_valid_connopts() -> + Port = quicer_test_lib:select_free_port(quic), + {ok, LH} = quicer_nif:listen(Port, maps:from_list(valid_server_listen_opts())), + ?FORALL( + ConnOpts, + quicer_conn_opts(), + begin + COpts = maps:from_list(ConnOpts ++ valid_client_conn_opts()), + case + quicer_nif:async_connect( + "localhost", + Port, + COpts + ) + of + {ok, ConnHandle} -> + quicer:close_listener(LH), + quicer_nif:async_shutdown_connection(ConnHandle, 0, 0), + collect(ok, true); + E -> + quicer:close_listener(LH), + collect(E, true) + end + end + ). + +prop_robust_async_accept_2() -> + ?FORALL( + {LH, AcceptOpts}, + {any(), any()}, + begin + case quicer_nif:async_accept(LH, AcceptOpts) of + {ok, _ConnHandle} -> + quicer:close_listener(LH), + collect(ok, true); + E -> + quicer:close_listener(LH), + collect(E, true) + end + end + ). + +%% accept on valid listener handle +prop_async_accept_2() -> + ?FORALL( + {#prop_handle{type = listener, handle = LH, destructor = Destroy}, AcceptOpts}, + {valid_listen_handle(), quicer_acceptor_opts()}, + begin + AOpts = maps:from_list(AcceptOpts), + case quicer_nif:async_accept(LH, AOpts) of + {ok, _ConnHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +%% 'active_n' always >= 0 +prop_async_accept_2_with_active() -> + ?FORALL( + {#prop_handle{type = listener, handle = LH, destructor = Destroy}, ActiveN}, + {valid_listen_handle(), oneof([boolean(), integer()])}, + begin + case quicer_nif:async_accept(LH, #{active => ActiveN}) of + {ok, ConnHandle} -> + quicer:close_connection(ConnHandle), + Destroy(), + collect(ok, quicer:getopt(ConnHandle, active) >= 0); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_start_stream() -> + ?FORALL( + {ConnHandle, StreamOpts}, + {any(), any()}, + begin + case quicer_nif:start_stream(ConnHandle, StreamOpts) of + {ok, _StreamHandle} -> + quicer:close_connection(ConnHandle), + collect(ok, true); + E -> + quicer:close_connection(ConnHandle), + collect(E, true) + end + end + ). + +prop_start_stream_with_valid_conn_handle() -> + ?FORALL( + {#prop_handle{type = conn, handle = ConnHandle, destructor = Destroy}, StreamOpts}, + {valid_connection_handle(), any()}, + begin + case quicer_nif:start_stream(ConnHandle, StreamOpts) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_start_stream_with_valid_conn_handle_AND_mandatory() -> + %% active_n is mandatory + ?FORALL( + {#prop_handle{type = conn, handle = ConnHandle, destructor = Destroy}, StreamOpt, ActiveN}, + {valid_connection_handle(), map(), active_n()}, + begin + case quicer_nif:start_stream(ConnHandle, StreamOpt#{active => ActiveN}) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_csend() -> + ?FORALL( + {Handle, Data, Opts, Flags}, + {any(), any(), any(), any()}, + begin + case quicer_nif:csend(Handle, Data, Opts, Flags) of + {ok, _StreamHandle} -> + quicer:close_stream(Handle), + collect(ok, true); + E -> + quicer:close_stream(Handle), + collect(E, true) + end + end + ). + +prop_csend_with_valid_opts() -> + %% @NOTE, start could still fail with different combination of opts + ?FORALL( + {#prop_handle{type = conn, handle = ConnHandle, destructor = Destroy}, Data, Opts, Flags}, + {valid_connection_handle(), data(), quicer_stream_opts(), quicer_send_flags()}, + begin + SOpts = maps:from_list(Opts), + case quicer_nif:csend(ConnHandle, Data, SOpts, Flags) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + {error, closed} -> + Destroy(), + %% As we test closed (not started) conn handle + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_send_3() -> + ?FORALL( + {Handle, Data, Flags}, + {any(), any(), any()}, + begin + case quicer_nif:send(Handle, Data, Flags) of + {ok, _StreamHandle} -> + quicer:close_stream(Handle), + collect(ok, true); + E -> + collect(E, true) + end + end + ). + +prop_send_3() -> + ?FORALL( + {#prop_handle{type = stream, handle = StreamHandle, destructor = Destroy}, Data, Flags}, + {valid_stream_handle(), data(), quicer_send_flags()}, + begin + case quicer_nif:send(StreamHandle, Data, Flags) of + {ok, _} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_recv_2() -> + ?FORALL( + {Handle, Len}, + {any(), any()}, + begin + case quicer_nif:recv(Handle, Len) of + {ok, _Data} -> + quicer:close_stream(Handle), + collect(ok, true); + E -> + quicer:close_stream(Handle), + collect(E, true) + end + end + ). + +prop_recv_2_with_valid_stream_handle() -> + ?FORALL( + {#prop_handle{type = stream, handle = StreamHandle, destructor = Destroy}, Len}, + {valid_stream_handle(), non_neg_integer()}, + begin + quicer_nif:setopt(StreamHandle, active, false, false), + case quicer_nif:recv(StreamHandle, Len) of + {ok, Data} when Data == not_ready orelse is_binary(Data) -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_send_dgram() -> + ?FORALL( + {Handle, Data, Flags}, + {any(), any(), any()}, + begin + case quicer_nif:send_dgram(Handle, Data, Flags) of + {ok, _StreamHandle} -> + quicer:close_stream(Handle), + collect(ok, true); + E -> + collect(E, true) + end + end + ). + +prop_send_dgram_with_valid_opts() -> + ?FORALL( + {#prop_handle{type = conn, handle = ConnHandle, destructor = Destroy}, Data, Flags}, + {valid_connection_handle(), data(), quicer_send_flags()}, + begin + case quicer_nif:send_dgram(ConnHandle, Data, Flags) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_async_shutdown_stream() -> + ?FORALL( + {Handle, Flags, ErrorCode}, + {any(), any(), any()}, + begin + case quicer_nif:async_shutdown_stream(Handle, Flags, ErrorCode) of + {ok, _StreamHandle} -> + quicer:close_stream(Handle), + collect(ok, true); + E -> + collect(E, true) + end + end + ). + +prop_async_shutdown_stream_with_valid_stream_handle() -> + ?FORALL( + { + #prop_handle{type = stream, handle = StreamHandle, destructor = Destroy}, + Flags, + ErrorCode + }, + {valid_stream_handle(), uint32(), uint64()}, + begin + case quicer_nif:async_shutdown_stream(StreamHandle, Flags, ErrorCode) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_async_shutdown_stream_with_valid_stream_handle_AND_flags() -> + ?FORALL( + { + #prop_handle{type = stream, handle = StreamHandle, destructor = Destroy}, + Flags, + ErrorCode + }, + {valid_stream_handle(), valid_stream_shutdown_flags(), uint64()}, + begin + case quicer_nif:async_shutdown_stream(StreamHandle, Flags, ErrorCode) of + {ok, _StreamHandle} -> + Destroy(), + collect(ok, true); + E -> + Destroy(), + collect(E, true) + end + end + ). + +prop_robust_sockname() -> + ?FORALL( + Handle, + any(), + begin + {error, badarg} == quicer_nif:sockname(Handle) + end + ). + +prop_sockname() -> + ?FORALL( + #prop_handle{type = conn, handle = ConnHandle, destructor = Destroy}, + valid_connection_handle(), + begin + Res = + case quicer_nif:sockname(ConnHandle) of + {ok, _} -> ok; + E -> E + end, + Destroy(), + collect(Res, true) + end + ). + +prop_robust_getopt_3() -> + ?FORALL( + {Handle, Opt0, OptLevel}, + {any(), any(), any()}, + begin + Opt = + case Opt0 of + {Opt1, _} -> Opt1; + Opt1 -> Opt1 + end, + {error, badarg} == quicer_nif:getopt(Handle, Opt, OptLevel) + end + ). + +prop_getopt_3_with_valid_handle() -> + ?FORALL( + {Handle, Opt0, OptLevel}, + {valid_handle(), any(), any()}, + begin + Opt = + case Opt0 of + {Opt1, _} -> Opt1; + Opt1 -> Opt1 + end, + Res = quicer_nif:getopt(Handle#prop_handle.handle, Opt, OptLevel), + (Handle#prop_handle.destructor)(), + collect(Res, true) + end + ). + +prop_getopt_3_with_valid_handle_AND_param() -> + ?FORALL( + {Handle, Opt0, OptLevel}, + { + valid_handle(), + oneof([ + listen_opt(), + conn_opt(), + acceptor_opt(), + stream_opt() + ]), + optlevel() + }, + begin + Opt = + case Opt0 of + {Opt1, _} -> Opt1; + Opt1 -> Opt1 + end, + Res = quicer_nif:getopt(Handle#prop_handle.handle, Opt, OptLevel), + (Handle#prop_handle.destructor)(), + collect(Res, true) + end + ). + +prop_robust_setopt_4() -> + ?FORALL( + {Handle, Opt, OptLevel, Value}, + {any(), any(), any(), any()}, + begin + {error, badarg} == quicer_nif:setopt(Handle, Opt, OptLevel, Value) + end + ). + +prop_robust_setopt_4_with_valid_handle_AND_param() -> + ?FORALL( + {Handle, {Optname, Value}, OptLevel}, + { + valid_handle(), + oneof([ + listen_opt(), + conn_opt(), + acceptor_opt(), + stream_opt(), + quicer_setting() + ]), + optlevel() + }, + begin + Res = quicer_nif:setopt(Handle#prop_handle.handle, Optname, OptLevel, Value), + (Handle#prop_handle.destructor)(), + collect(Res, true) + end + ). + +prop_getopt_3_stream_opt() -> + ?FORALL( + {Handle, {Optname, _Value}}, + {valid_stream_handle(), stream_opt()}, + begin + Res = quicer_nif:getopt(Handle#prop_handle.handle, Optname, false), + (Handle#prop_handle.destructor)(), + collect(Res, true) + end + ). + +prop_getopt_3_conn_opt() -> + ?FORALL( + {Handle, {Optname, _Value}}, + {valid_connection_handle(), conn_opt()}, + begin + Res = quicer_nif:getopt(Handle#prop_handle.handle, Optname, false), + (Handle#prop_handle.destructor)(), + case Res of + {ok, _} -> + collect(ok, true); + _ -> + collect({Optname, Res}, true) + end + end + ). + +prop_robust_peercert() -> + ?FORALL( + Handle, + any(), + begin + {error, badarg} == quicer:peercert(Handle) + end + ). + +prop_peercert_with_valid_connection_handle() -> + ?FORALL( + #prop_handle{type = conn, handle = Handle, destructor = Destroy}, + valid_connection_handle(), + begin + Res = quicer_nif:peercert(Handle), + Destroy(), + collect(Res, true) + end + ). + +prop_peercert_with_valid_stream_handle() -> + ?FORALL( + #prop_handle{type = stream, handle = Handle, destructor = Destroy}, + valid_stream_handle(), + begin + Destroy(), + collect(quicer_nif:peercert(Handle), true) + end + ). + +prop_robust_controlling_process() -> + ?FORALL( + {Handle, Pid}, + {any(), any()}, + begin + {error, badarg} == quicer_nif:controlling_process(Handle, Pid) + end + ). + +prop_controlling_process_with_valid_opts() -> + ?FORALL( + {#prop_handle{type = Type, handle = Handle, destructor = Destroy}, Pid}, + {valid_handle(), pid()}, + begin + Res = quicer_nif:controlling_process(Handle, Pid), + case Res of + ok when Type == conn -> + {ok, Pid} = quicer_nif:get_conn_owner(Handle); + ok when Type == stream -> + {ok, Pid} = quicer_nif:get_stream_owner(Handle); + _ -> + skip + end, + Destroy(), + collect({Type, Res}, true) + end + ). + +%%% ============================================================================ +%%% Generators +%%% ============================================================================ diff --git a/test/prop_stateful_conn.erl b/test/prop_stateful_conn.erl new file mode 100644 index 00000000..981f2c07 --- /dev/null +++ b/test/prop_stateful_conn.erl @@ -0,0 +1,270 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 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(prop_stateful_conn). +-compile([export_all]). +-include_lib("proper/include/proper.hrl"). +-include_lib("quicer/include/quicer_types.hrl"). +-include("prop_quic_types.hrl"). + +%% Model Callbacks +-export([ + command/1, + initial_state/0, + next_state/3, + precondition/2, + postcondition/3 +]). + +%%%%%%%%%%%%%%%%%% +%%% PROPERTIES %%% +%%%%%%%%%%%%%%%%%% +prop_client_state_test(opts) -> + [{numtests, 2000}]. +prop_client_state_test() -> + {ok, _} = listener_start_link(?MODULE), + ?FORALL( + Cmds, + commands(?MODULE), + begin + {History, State, Result} = run_commands(?MODULE, Cmds), + ?WHENFAIL( + io:format( + "History: ~p\nState: ~p\nResult: ~p\n", + [History, State, Result] + ), + aggregate(command_names(Cmds), Result =:= ok) + ) + end + ). + +%%%%%%%%%%%%% +%%% MODEL %%% +%%%%%%%%%%%%% +%% @doc Initial model value at system start. Should be deterministic. +initial_state() -> + {ok, H} = quicer:connect("localhost", 14568, default_conn_opts(), 10000), + #{ + state => connected, + handle => H, + owner => self(), + me => self() + }. + +%% @doc List of possible commands to run against the system +%% +command(#{handle := Handle}) -> + frequency([ + {100, {call, quicer, getopt, [Handle, ?LET({Opt, _}, conn_opt(), Opt)]}}, + {100, + {call, quicer, async_accept_stream, [Handle, ?LET(Opts, quicer_acceptor_opts(), Opts)]}}, + {100, {call, quicer, peername, [Handle]}}, + {50, {call, quicer, peercert, [Handle]}}, + {10, {call, quicer, negotiated_protocol, [Handle]}}, + {10, {call, quicer, get_connections, []}}, + {10, {call, quicer, get_conn_owner, [Handle]}}, + {1, {call, quicer, controlling_process, [Handle, ?LET(Pid, quicer_prop_gen:pid(), Pid)]}}, + {100, + {call, quicer, async_csend, [ + Handle, + ?LET(Bin, binary(), Bin), + quicer_prop_gen:valid_csend_stream_opts(), + ?valid_flags(send_flags()) + ]}}, + {50, {call, quicer, close_connection, [Handle]}}, + {50, + {call, quicer, shutdown_connection, [ + Handle, ?valid_flags(conn_shutdown_flag()), ?LET(ErrorCode, uint62(), ErrorCode) + ]}} + ]). + +%% @doc Determines whether a command should be valid under the +%% current state. +precondition(#{state := connected}, {call, _Mod, _Fun, _Args}) -> + true; +precondition(#{state := closed}, {call, _Mod, _Fun, _Args}) -> + true; +precondition(_State, {call, _Mod, _Fun, _Args}) -> + false. + +%% @doc Given the state `State' *prior* to the call +%% `{call, Mod, Fun, Args}', determine whether the result +%% `Res' (coming from the actual system) makes sense. +postcondition(_State, {call, quicer, getopt, _Args}, {ok, _}) -> + true; +postcondition(_State, {call, quicer, getopt, [_, password]}, {error, badarg}) -> + true; +postcondition(_State, {call, quicer, getopt, [_, NotSupp]}, {error, not_supported}) when + NotSupp == statistics_plat orelse + NotSupp == resumption_ticket +-> + true; +postcondition(_State, {call, quicer, getopt, [_, SetOnly]}, {error, param_error}) when + SetOnly =:= nst orelse + SetOnly =:= cibir_id orelse + SetOnly =:= cacertfile orelse + SetOnly =:= keyfile orelse + SetOnly =:= certfile orelse + SetOnly =:= password orelse + SetOnly =:= local_interface orelse + SetOnly =:= tls_secrets orelse + SetOnly =:= alpn orelse + SetOnly =:= sslkeylogfile orelse + SetOnly =:= verify orelse + SetOnly =:= handshake_idle_timeout_ms orelse + %% @TODO. unimpl. + SetOnly =:= version_settings orelse + %% @TODO. unimpl. + SetOnly =:= statistics_v2 orelse + %% @TODO. unimpl. + SetOnly =:= statistics_v2_plat orelse + SetOnly =:= quic_event_mask +-> + true; +postcondition(_State, {call, quicer, getopt, [_, SetOnly]}, {error, invalid_parameter}) when + SetOnly =:= local_interface orelse + SetOnly =:= peer_certificate_valid +-> + true; +postcondition(_State, {call, quicer, getopt, [_, close_reason_phrase]}, {error, not_found}) -> + %% @NOTE, msquic returns not_found whne it is not set. + true; +postcondition(_State, {call, quicer, async_csend, _}, {ok, _}) -> + %% relaxed check on csend + true; +postcondition(_State, {call, quicer, async_csend, _Args}, {error, stm_open_error, invalid_state}) -> + true; +postcondition(_State, {call, quicer, async_accept_stream, _Args}, {ok, _}) -> + true; +postcondition( + _State, {call, quicer, async_accept_stream, _Args}, {error, stm_open_error, invalid_state} +) -> + true; +postcondition(_State, {call, quicer, close_connection, _Args}, ok) -> + true; +postcondition(_State, {call, quicer, shutdown_connection, _Args}, ok) -> + true; +postcondition( + #{me := Me, owner := Owner, state := State}, + {call, quicer, shutdown_connection, _Args}, + {error, timeout} +) when Me =/= Owner orelse State == closed -> + true; +postcondition( + #{me := Me, owner := Owner, state := State}, + {call, quicer, close_connection, [_]}, + {error, timeout} +) when Me =/= Owner orelse State == closed -> + true; +postcondition(_State, {call, quicer, negotiated_protocol, [_]}, {ok, <<"prop">>}) -> + true; +postcondition(_State, {call, quicer, peername, [_]}, {ok, {_, 14568}}) -> + true; +postcondition(_State, {call, quicer, peercert, [_]}, {error, no_peercert}) -> + true; +postcondition(_State, {call, quicer, controlling_process, [_, _]}, ok) -> + true; +postcondition(_State, {call, quicer, get_conn_owner, _}, {ok, Pid}) when is_pid(Pid) -> + true; +postcondition( + #{owner := Owner, state := connected}, + {call, quicer, controlling_process, [_, _]}, + {error, not_owner} +) -> + Owner =/= self(); +postcondition( + #{owner := Owner, state := connected}, + {call, quicer, controlling_process, [_, _]}, + {error, owner_dead} +) -> + Owner =/= self(); +%% postcondition(#{owner := Owner, state := closed} = State, {call, quicer, controlling_process, [_, _]}, {error, not_owner}) -> +%% true; +postcondition(#{handle := H, state := connected}, {call, quicer, get_connections, _}, Conns) -> + lists:member(H, Conns); +postcondition(#{handle := _H, state := closed}, {call, quicer, get_connections, _}, _Conns) -> + %% May or may not in Conns deps on the timing + true; +postcondition(#{state := closed}, {call, _Mod, _Fun, _Args}, {error, closed}) -> + true; +postcondition(_State, {call, _Mod, _Fun, _Args} = _Call, _Res) -> + false. + +%% @doc Assuming the postcondition for a call was true, update the model +%% accordingly for the test to proceed. +next_state(#{state := connected} = State, _Res, {call, quicer, close_connection, _Args}) -> + State#{state := closed}; +next_state(#{state := connected} = State, _Res, {call, quicer, shutdown_connection, _Args}) -> + State#{state := closed}; +next_state( + #{state := connected} = State, ok, {call, quicer, controlling_process, [_, Owner]} +) -> + State#{owner := Owner}; +next_state(State, _Res, {call, _Mod, _Fun, _Args}) -> + State. + +%%% Generators + +%%%%%%%%%%%%%%%%%%%%%%% +%%% Listener helper %%% +%%%%%%%%%%%%%%%%%%%%%%% +listener_start_link(ListenerName) -> + application:ensure_all_started(quicer), + LPort = 14568, + ListenerOpts = default_listen_opts(), + ConnectionOpts = [ + {conn_callback, example_server_connection}, + {stream_acceptors, 32} + | default_conn_opts() + ], + StreamOpts = [ + {stream_callback, example_server_stream} + ], + Options = {ListenerOpts, ConnectionOpts, StreamOpts}, + quicer:spawn_listener(ListenerName, LPort, Options). + +%% OS picks the available port +select_port() -> + {ok, S} = gen_udp:open(0, [{reuseaddr, true}]), + {ok, {_, Port}} = inet:sockname(S), + gen_udp:close(S), + Port. + +default_listen_opts() -> + [ + {conn_acceptors, 32}, + {cacertfile, "./msquic/submodules/openssl/test/certs/rootCA.pem"}, + {certfile, "./msquic/submodules/openssl/test/certs/servercert.pem"}, + {keyfile, "./msquic/submodules/openssl/test/certs/serverkey.pem"}, + {alpn, ["prop"]}, + {verify, none}, + {idle_timeout_ms, 0}, + %% some CI runner is slow on this + {handshake_idle_timeout_ms, 10000}, + % QUIC_SERVER_RESUME_AND_ZERORTT + {server_resumption_level, 2}, + {peer_bidi_stream_count, 10} + ]. + +default_conn_opts() -> + [ + {alpn, ["prop"]}, + %% , {sslkeylogfile, "/tmp/SSLKEYLOGFILE"} + {verify, none}, + {idle_timeout_ms, 0}, + {cacertfile, "./msquic/submodules/openssl/test/certs/rootCA.pem"}, + {certfile, "./msquic/submodules/openssl/test/certs/servercert.pem"}, + {keyfile, "./msquic/submodules/openssl/test/certs/serverkey.pem"} + ]. diff --git a/test/quicer_SUITE.erl b/test/quicer_SUITE.erl index be8ca603..a11e45c9 100644 --- a/test/quicer_SUITE.erl +++ b/test/quicer_SUITE.erl @@ -901,7 +901,7 @@ tc_conn_and_stream_shared_owner(Config) -> ensure_server_exit_normal(Ref). %% tc_getopt_raw(Config) -> -%% Parm = param_conn_quic_version, +%% Parm = quic_version, %% Port = select_port(), %% Owner = self(), %% {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), @@ -922,7 +922,7 @@ tc_conn_and_stream_shared_owner(Config) -> %% end. tc_getopt(Config) -> - Parm = param_conn_statistics, + Parm = statistics, Port = select_port(), Owner = self(), {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), @@ -930,33 +930,33 @@ tc_getopt(Config) -> listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), {ok, Stats} = quicer:getopt(Conn, Parm, false), - ?assertEqual({ok, false}, quicer:getopt(Conn, param_conn_datagram_receive_enabled)), - ?assertEqual({ok, false}, quicer:getopt(Conn, param_conn_datagram_send_enabled)), - ?assertEqual({ok, false}, quicer:getopt(Conn, param_conn_disable_1rtt_encryption)), - ?assertEqual({ok, 1}, quicer:getopt(Conn, param_conn_quic_version)), + ?assertEqual({ok, false}, quicer:getopt(Conn, datagram_receive_enabled)), + ?assertEqual({ok, false}, quicer:getopt(Conn, datagram_send_enabled)), + ?assertEqual({ok, false}, quicer:getopt(Conn, disable_1rtt_encryption)), + ?assertEqual({ok, 1}, quicer:getopt(Conn, quic_version)), %% 0: fifo %% 1: round-robin - ?assertEqual({ok, 0}, quicer:getopt(Conn, param_conn_stream_scheduling_scheme)), - ?assertMatch({ok, {_, _}}, quicer:getopt(Conn, param_conn_local_address)), - {ok, MaxIds} = quicer:getopt(Conn, param_conn_max_stream_ids), + ?assertEqual({ok, 0}, quicer:getopt(Conn, stream_scheduling_scheme)), + ?assertMatch({ok, {_, _}}, quicer:getopt(Conn, local_address)), + {ok, MaxIds} = quicer:getopt(Conn, max_stream_ids), ct:pal( "MaxStreamIds: client bidi: ~p, server bidi: ~p " "client unidi ~p, server unidi ~p", MaxIds ), ?assertEqual( - {error, invalid_parameter}, quicer:getopt(Conn, param_conn_local_interface) + {error, invalid_parameter}, quicer:getopt(Conn, local_interface) ), ?assertEqual( - {error, invalid_parameter}, quicer:getopt(Conn, param_conn_peer_certificate_valid) + {error, invalid_parameter}, quicer:getopt(Conn, peer_certificate_valid) ), - {error, not_supported} = quicer:getopt(Conn, param_conn_resumption_ticket), + {error, not_supported} = quicer:getopt(Conn, resumption_ticket), 0 = proplists:get_value("Recv.DroppedPackets", Stats), [ true = proplists:is_defined(SKey, Stats) || SKey <- ["Send.TotalPackets", "Recv.TotalPackets"] ], - {ok, Settings} = quicer:getopt(Conn, param_conn_settings, false), + {ok, Settings} = quicer:getopt(Conn, settings, false), 5000 = proplists:get_value(idle_timeout_ms, Settings), true = proplists:get_value(send_buffering_enabled, Settings), {ok, Stm} = quicer:start_stream(Conn, []), @@ -965,8 +965,9 @@ tc_getopt(Config) -> {quic, <<"ping">>, Stm, _} -> ok end, ok = quicer:close_connection(Conn), - %% @todo unsupp in msquic, leave it for now - {error, _} = quicer:getopt(Conn, param_conn_close_reason_phrase), + %% @NOTE: msquic returns not_found when it is unset. + {error, Reason} = quicer:getopt(Conn, close_reason_phrase), + ?assert(Reason == not_found orelse Reason == closed), SPid ! done, ensure_server_exit_normal(Ref) after 5000 -> @@ -980,10 +981,10 @@ tc_getopt_settings(Config) -> receive listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, Settings} = quicer:getopt(Conn, param_conn_settings, false), + {ok, Settings} = quicer:getopt(Conn, settings, false), ?assertEqual( {ok, Settings}, - quicer:getopt(Conn, param_configuration_settings, quic_configuration) + quicer:getopt(Conn, settings, quic_configuration) ), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), @@ -991,12 +992,12 @@ tc_getopt_settings(Config) -> {quic, <<"ping">>, Stm, _} -> ok end, ?assertEqual( - {ok, Settings}, quicer:getopt(Stm, param_configuration_settings, quic_configuration) + {ok, Settings}, quicer:getopt(Stm, settings, quic_configuration) ), ?assertEqual( - ok, quicer:setopt(quic_global, param_global_settings, #{idle_timeout_ms => 12000}) + ok, quicer:setopt(quic_global, settings, #{idle_timeout_ms => 12000}) ), - {ok, NewGSettings} = quicer:getopt(quic_global, param_global_settings), + {ok, NewGSettings} = quicer:getopt(quic_global, settings), ?assertEqual(12000, proplists:get_value(idle_timeout_ms, NewGSettings)), ok = quicer:close_connection(Conn), SPid ! done, @@ -1090,9 +1091,9 @@ tc_get_stream_0rtt_length(Config) -> ok end, %% before stream shutdown, - {error, invalid_state} = quicer:getopt(Stm, param_stream_0rtt_length), + {error, invalid_state} = quicer:getopt(Stm, '0rtt_length'), quicer:async_shutdown_stream(Stm), - case quicer:getopt(Stm, param_stream_0rtt_length) of + case quicer:getopt(Stm, '0rtt_length') of {ok, Val} -> ?assert(is_integer(Val)); {error, invalid_state} -> ok; {error, closed} -> ok @@ -1118,7 +1119,7 @@ tc_get_stream_ideal_sndbuff_size(Config) -> ok end, %% before stream shutdown, - {ok, Val} = quicer:getopt(Stm, param_stream_ideal_send_buffer_size), + {ok, Val} = quicer:getopt(Stm, ideal_send_buffer_size), ?assert(is_integer(Val)), ok = quicer:shutdown_stream(Stm), SPid ! done, @@ -1227,7 +1228,7 @@ tc_alpn(Config) -> listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), {ok, {_, _}} = quicer:sockname(Conn), - {ok, <<"sample">>} = quicer:getopt(Conn, param_tls_negotiated_alpn, quic_tls), + {ok, <<"sample">>} = quicer:getopt(Conn, negotiated_alpn, quic_tls), {ok, <<"sample">>} = quicer:negotiated_protocol(Conn), ok = quicer:close_connection(Conn), SPid ! done @@ -1306,8 +1307,8 @@ tc_setopt_conn_settings(Config) -> receive listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, Settings} = quicer:getopt(Conn, param_conn_settings), - ok = quicer:setopt(Conn, param_conn_settings, Settings), + {ok, Settings} = quicer:getopt(Conn, settings), + ok = quicer:setopt(Conn, settings, Settings), {ok, Stm0} = quicer:start_stream(Conn, [{active, false}]), %% Stream 0 {ok, 5} = quicer:send(Stm0, <<"ping0">>), @@ -1364,32 +1365,33 @@ tc_setopt(Config) -> ok end, - {error, not_supported} = quicer:setopt(Conn, param_conn_quic_version, 1), + {error, not_supported} = quicer:setopt(Conn, quic_version, 1), %% must be set before start - {error, invalid_state} = quicer:setopt(Conn, param_conn_remote_address, "8.8.8.8:443"), - - {error, not_supported} = quicer:setopt(Conn, param_conn_ideal_processor, 1), - {error, not_supported} = quicer:setopt(Conn, param_conn_max_stream_ids, [1, 2, 3, 4]), - ok = quicer:setopt(Conn, param_conn_close_reason_phrase, "You are not welcome!"), - ok = quicer:setopt(Conn, param_conn_stream_scheduling_scheme, 1), - {ok, 1} = quicer:getopt(Conn, param_conn_stream_scheduling_scheme), + {error, invalid_state} = quicer:setopt(Conn, remote_address, "8.8.8.8:443"), + + {error, not_supported} = quicer:setopt(Conn, ideal_processor, 1), + {error, not_supported} = quicer:setopt(Conn, max_stream_ids, [1, 2, 3, 4]), + ok = quicer:setopt(Conn, close_reason_phrase, "You are not welcomed!"), + {ok, <<"You are not welcomed!">>} = quicer:getopt(Conn, close_reason_phrase), + ok = quicer:setopt(Conn, stream_scheduling_scheme, 1), + {ok, 1} = quicer:getopt(Conn, stream_scheduling_scheme), %% get-only {error, invalid_parameter} = quicer:setopt( - Conn, param_conn_datagram_send_enabled, false + Conn, datagram_send_enabled, false ), %% Must set before start {error, invalid_state} = quicer:setopt( - Conn, param_conn_datagram_receive_enabled, false + Conn, datagram_receive_enabled, false ), - {error, invalid_state} = quicer:setopt(Conn, param_conn_datagram_receive_enabled, true), + {error, invalid_state} = quicer:setopt(Conn, datagram_receive_enabled, true), {error, invalid_state} = quicer:setopt( - Conn, param_conn_datagram_receive_enabled, false + Conn, datagram_receive_enabled, false ), - ok = quicer:setopt(Conn, param_conn_peer_certificate_valid, true), - ok = quicer:setopt(Conn, param_conn_peer_certificate_valid, false), - {error, invalid_state} = quicer:setopt(Conn, param_conn_local_interface, 1), + ok = quicer:setopt(Conn, peer_certificate_valid, true), + ok = quicer:setopt(Conn, peer_certificate_valid, false), + {error, invalid_state} = quicer:setopt(Conn, local_interface, 1), %% test invalid - {error, invalid_parameter} = quicer:setopt(Conn, param_conn_resumption_ticket, <<>>), + {error, invalid_parameter} = quicer:setopt(Conn, resumption_ticket, <<>>), %% unblock Stream 1 SPid ! {set_stm_cnt, 3}, @@ -1407,8 +1409,8 @@ tc_setopt(Config) -> tc_setopt_remote_addr(_Config) -> {ok, Conn} = quicer:open_connection(), - ok = quicer:setopt(Conn, param_conn_remote_address, "8.8.8.8:443"), - ?assertEqual({ok, {{8, 8, 8, 8}, 443}}, quicer:getopt(Conn, param_conn_remote_address)), + ok = quicer:setopt(Conn, remote_address, "8.8.8.8:443"), + ?assertEqual({ok, {{8, 8, 8, 8}, 443}}, quicer:getopt(Conn, remote_address)), quicer:shutdown_connection(Conn). tc_setopt_bad_opt(_Config) -> @@ -1443,17 +1445,17 @@ tc_setopt_config_settings(Config) -> receive listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), - {ok, Settings} = quicer:getopt(Conn, param_conn_settings, false), + {ok, Settings} = quicer:getopt(Conn, settings, false), ?assertEqual( ok, quicer:setopt( Conn, - param_configuration_settings, + settings, #{idle_timeout_ms => 60000}, quic_configuration ) ), - {ok, Settings} = quicer:getopt(Conn, param_conn_settings, false), + {ok, Settings} = quicer:getopt(Conn, settings, false), {ok, Stm} = quicer:start_stream(Conn, []), {ok, 4} = quicer:send(Stm, <<"ping">>), receive @@ -1464,11 +1466,11 @@ tc_setopt_config_settings(Config) -> %% config resources are not really exposed ?assertEqual( {ok, Settings1}, - quicer:getopt(Conn, param_configuration_settings, quic_configuration) + quicer:getopt(Conn, settings, quic_configuration) ), ?assertEqual( {ok, Settings1}, - quicer:getopt(Stm, param_configuration_settings, quic_configuration) + quicer:getopt(Stm, settings, quic_configuration) ), ok = quicer:close_connection(Conn), SPid ! done, @@ -1479,8 +1481,8 @@ tc_setopt_config_settings(Config) -> tc_setopt_conn_remote_addr(_Config) -> {ok, Conn} = quicer:open_connection(), - ok = quicer:setopt(Conn, param_conn_remote_address, "8.8.8.8:443"), - ok = quicer:setopt(Conn, param_conn_datagram_receive_enabled, false), + ok = quicer:setopt(Conn, remote_address, "8.8.8.8:443"), + ok = quicer:setopt(Conn, datagram_receive_enabled, false), Res = quicer:connect( "google.com", 443, @@ -1504,32 +1506,32 @@ tc_setopt_conn_remote_addr(_Config) -> end. tc_setopt_global_retry_mem_percent(_Config) -> - ?assertEqual(ok, quicer:setopt(quic_global, param_global_retry_memory_percent, 30, false)). + ?assertEqual(ok, quicer:setopt(quic_global, retry_memory_percent, 30, false)). tc_getopt_global_retry_mem_percent(_Config) -> - {ok, Val} = quicer:getopt(quic_global, param_global_retry_memory_percent), + {ok, Val} = quicer:getopt(quic_global, retry_memory_percent), ?assert(is_integer(Val)). tc_getopt_global_lb_mode(_Config) -> ?assertEqual( {ok, 0}, - quicer:getopt(quic_global, param_global_load_balacing_mode) + quicer:getopt(quic_global, load_balacing_mode) ). tc_getopt_global_lib_git_hash(_Config) -> - {ok, HashBin} = quicer:getopt(quic_global, param_global_library_git_hash), + {ok, HashBin} = quicer:getopt(quic_global, library_git_hash), ct:pal("msquic git hash ~s", [HashBin]), ?assert(is_binary(HashBin)). tc_setopt_global_lb_mode(_Config) -> ?assertEqual( {error, badarg}, - quicer:setopt(quic_global, param_global_load_balacing_mode, 4) + quicer:setopt(quic_global, load_balacing_mode, 4) ), %% v1 api ?assertEqual( {error, invalid_parameter}, - quicer:setopt(quic_global, param_global_load_balacing_mode, 1) + quicer:setopt(quic_global, load_balacing_mode, 1) ). tc_setopt_conn_local_addr(Config) -> @@ -1555,13 +1557,13 @@ tc_setopt_conn_local_addr(Config) -> end, {ok, OldAddr} = quicer:sockname(Conn), %% change local addr with a new random port (0) - ?assertEqual(ok, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:0")), + ?assertEqual(ok, quicer:setopt(Conn, local_address, "127.0.0.1:0")), {ok, NewAddr} = quicer:sockname(Conn), ?assertNotEqual(OldAddr, NewAddr), ?assertNotEqual({ok, {{127, 0, 0, 1}, 50600}}, NewAddr), ?assertNotEqual({ok, {{127, 0, 0, 1}, 50600}}, OldAddr), %% change local addr with a new port 5060 - ?assertEqual(ok, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:50600")), + ?assertEqual(ok, quicer:setopt(Conn, local_address, "127.0.0.1:50600")), receive {quic, peer_address_changed, Conn, NewPeerAddr} -> ct:pal("new peer addr: ~p", [NewPeerAddr]) @@ -1618,7 +1620,7 @@ tc_setopt_conn_local_addr_in_use(Config) -> end, {ok, OldAddr} = quicer:sockname(Conn), %% change local addr with a new random port (0) - ?assertEqual(ok, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:0")), + ?assertEqual(ok, quicer:setopt(Conn, local_address, "127.0.0.1:0")), %% sleep is needed to finish migration at protocol level timer:sleep(50), {ok, NewAddr} = quicer:sockname(Conn), @@ -1630,7 +1632,7 @@ tc_setopt_conn_local_addr_in_use(Config) -> {ok, ESocket} = gen_udp:open(50600, [{ip, element(1, NewAddr)}]), %% change local addr with a new port 5060 ?assertEqual( - {error, address_in_use}, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:50600") + {error, address_in_use}, quicer:setopt(Conn, local_address, "127.0.0.1:50600") ), gen_udp:close(ESocket), @@ -1661,11 +1663,11 @@ tc_setopt_stream_priority(Config) -> listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), - ok = quicer:setopt(Stm, param_stream_priority, 10), + ok = quicer:setopt(Stm, priority, 10), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, <<"ping">>} = quicer:recv(Stm, 0), % try to set priority out of range - {error, param_error} = quicer:setopt(Stm, param_stream_priority, 65536), + {error, param_error} = quicer:setopt(Stm, priority, 65536), SPid ! done, ensure_server_exit_normal(Ref) after 5000 -> @@ -1680,18 +1682,18 @@ tc_setopt_stream_unsupp_opts(Config) -> listener_ready -> {ok, Conn} = quicer:connect("localhost", Port, default_conn_opts(), 5000), {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), - ?assertEqual({error, not_supported}, quicer:setopt(Stm, param_stream_id, 8)), + ?assertEqual({error, not_supported}, quicer:setopt(Stm, stream_id, 8)), ?assertEqual( - {error, not_supported}, quicer:setopt(Stm, param_stream_0rtt_length, 4096) + {error, not_supported}, quicer:setopt(Stm, '0rtt_length', 4096) ), ?assertEqual( {error, not_supported}, - quicer:setopt(Stm, param_stream_ideal_send_buffer_size, 4096) + quicer:setopt(Stm, ideal_send_buffer_size, 4096) ), {ok, 4} = quicer:send(Stm, <<"ping">>), {ok, <<"ping">>} = quicer:recv(Stm, 0), % try to set priority out of range - {error, param_error} = quicer:setopt(Stm, param_stream_priority, 65536), + {error, param_error} = quicer:setopt(Stm, priority, 65536), quicer:shutdown_stream(Stm), SPid ! done, ensure_server_exit_normal(Ref) @@ -2112,7 +2114,7 @@ tc_stream_start_flag_shutdown_on_fail(Config) -> 100, 10, fun() -> - {error, closed} = quicer:getopt(Stm, param_configuration_settings, quic_configuration) + {error, closed} = quicer:getopt(Stm, settings, quic_configuration) end ), ?assert(is_integer(Rid)). @@ -2418,7 +2420,7 @@ tc_insecure_traffic(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ], 5000 @@ -2427,7 +2429,7 @@ tc_insecure_traffic(Config) -> {ok, 5} = quicer:async_send(Stm, <<"ping1">>), receive {quic, <<"ping1">>, Stm, _} -> - {ok, true} = quicer:getopt(Conn, param_conn_disable_1rtt_encryption, false), + {ok, true} = quicer:getopt(Conn, disable_1rtt_encryption, false), ok end, quicer:close_connection(Conn), @@ -2460,7 +2462,7 @@ tc_event_start_compl_client(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ], 5000 @@ -2523,7 +2525,7 @@ tc_event_start_compl_server(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ], 5000 @@ -2587,7 +2589,7 @@ tc_direct_send_over_conn(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ] ), @@ -2672,7 +2674,7 @@ tc_direct_send_over_conn_block(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ] ), @@ -2750,7 +2752,7 @@ tc_direct_send_over_conn_fail(Config) -> "localhost", Port, [ - {param_conn_disable_1rtt_encryption, true} + {disable_1rtt_encryption, true} | default_conn_opts() ] ), @@ -2820,10 +2822,10 @@ tc_getopt_tls_handshake_info(Config) -> key_exchange_strength := 0, tls_protocol_version := tlsv1_3 } = HSInfo} = - quicer:getopt(Conn, param_tls_handshake_info, quic_tls), + quicer:getopt(Conn, handshake_info, quic_tls), ?assertEqual( {error, not_supported}, - quicer:setopt(Conn, param_tls_handshake_info, HSInfo, quic_tls) + quicer:setopt(Conn, handshake_info, HSInfo, quic_tls) ), ok = quicer:close_connection(Conn), SPid ! done @@ -3069,7 +3071,7 @@ echo_server(Owner, Config, Port) -> ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [ BidirCount, UniDirCount ]), - quicer:setopt(Conn, param_conn_settings, #{ + quicer:setopt(Conn, settings, #{ peer_bidi_stream_count => BidirCount, peer_unidi_stream_count => UniDirCount }), @@ -3139,7 +3141,7 @@ echo_server_stm_loop(L, Conn, Stms) -> 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 = quicer:setopt(Conn, settings, #{peer_bidi_stream_count => N}), {ok, NewStm} = quicer:accept_stream(Conn, []), echo_server_stm_loop(L, Conn, [NewStm | Stms]); {peer_addr, From} -> @@ -3149,7 +3151,7 @@ echo_server_stm_loop(L, Conn, Stms) -> ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [ BidirCount, UniDirCount ]), - quicer:setopt(Conn, param_conn_settings, #{ + quicer:setopt(Conn, settings, #{ peer_bidi_stream_count => BidirCount, peer_unidi_stream_count => UniDirCount }), diff --git a/test/quicer_connection_SUITE.erl b/test/quicer_connection_SUITE.erl index d1d8bb77..552873e0 100644 --- a/test/quicer_connection_SUITE.erl +++ b/test/quicer_connection_SUITE.erl @@ -238,7 +238,7 @@ tc_conn_basic_verify_peer(Config) -> 5000 ), {ok, {_, _}} = quicer:sockname(Conn), - {ok, Info} = quicer:getopt(Conn, param_tls_handshake_info, quic_tls), + {ok, Info} = quicer:getopt(Conn, handshake_info, quic_tls), ct:pal("Handshake Info with Google: ~p", [Info]), ok = quicer:close_connection(Conn), ok. @@ -394,7 +394,7 @@ tc_conn_with_localaddr(Config) -> "127.0.0.1", Port, [ - {param_conn_local_address, "127.0.0.1:" ++ integer_to_list(PortX)} + {local_address, "127.0.0.1:" ++ integer_to_list(PortX)} | default_conn_opts(Config) ], 5000 @@ -759,7 +759,7 @@ tc_conn_opt_ideal_processor(Config) -> {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), + {ok, Processor} = quicer:getopt(Conn, ideal_processor), ?assert(is_integer(Processor)), ok = quicer:close_connection(Conn), SPid ! done @@ -776,12 +776,12 @@ tc_conn_opt_share_udp_binding(Config) -> {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), + {ok, IsShared} = quicer:getopt(Conn, share_udp_binding), ?assert(is_boolean(IsShared)), {error, invalid_state} = quicer:setopt( - Conn, param_conn_share_udp_binding, not IsShared + Conn, share_udp_binding, not IsShared ), - {ok, IsShared} = quicer:getopt(Conn, param_conn_share_udp_binding), + {ok, IsShared} = quicer:getopt(Conn, share_udp_binding), ok = quicer:close_connection(Conn), SPid ! done after 5000 -> @@ -797,10 +797,10 @@ tc_conn_opt_local_bidi_stream_count(Config) -> {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), + {ok, Cnt} = quicer:getopt(Conn, local_bidi_stream_count), ?assert(is_integer(Cnt)), {error, invalid_parameter} = quicer:setopt( - Conn, param_conn_local_bidi_stream_count, Cnt + 2 + Conn, local_bidi_stream_count, Cnt + 2 ), ok = quicer:close_connection(Conn), SPid ! done @@ -817,10 +817,10 @@ tc_conn_opt_local_uni_stream_count(Config) -> {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), + {ok, Cnt} = quicer:getopt(Conn, local_unidi_stream_count), ?assert(is_integer(Cnt)), {error, invalid_parameter} = quicer:setopt( - Conn, param_conn_local_unidi_stream_count, Cnt + 2 + Conn, local_unidi_stream_count, Cnt + 2 ), ok = quicer:close_connection(Conn), SPid ! done @@ -847,7 +847,7 @@ tc_conn_list(Config) -> {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), + {ok, Cnt} = quicer:getopt(Conn, local_unidi_stream_count), ?assert(is_integer(Cnt)), Conns = case Reg of @@ -1050,7 +1050,7 @@ echo_server(Owner, Config, Port) -> ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [ BidirCount, UniDirCount ]), - quicer:setopt(Conn, param_conn_settings, #{ + quicer:setopt(Conn, settings, #{ peer_bidi_stream_count => BidirCount, peer_unidi_stream_count => UniDirCount }), @@ -1121,7 +1121,7 @@ echo_server_stm_loop(L, Conn, Stms) -> 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 = quicer:setopt(Conn, settings, #{peer_bidi_stream_count => N}), {ok, NewStm} = quicer:accept_stream(Conn, []), echo_server_stm_loop(L, Conn, [NewStm | Stms]); {peer_addr, From} -> @@ -1131,7 +1131,7 @@ echo_server_stm_loop(L, Conn, Stms) -> ct:pal("echo server stream flow control to bidirectional: ~p : ~p", [ BidirCount, UniDirCount ]), - quicer:setopt(Conn, param_conn_settings, #{ + quicer:setopt(Conn, settings, #{ peer_bidi_stream_count => BidirCount, peer_unidi_stream_count => UniDirCount }), diff --git a/test/quicer_echo_server_stream_callback.erl b/test/quicer_echo_server_stream_callback.erl index 71fdb27b..f8d5d613 100644 --- a/test/quicer_echo_server_stream_callback.erl +++ b/test/quicer_echo_server_stream_callback.erl @@ -36,7 +36,7 @@ -export([handle_stream_data/4]). %% @doc handle handoff from other stream owner. -init_handoff(Stream, StreamOpts, Conn, #{is_orphan := true, flags := Flags}) -> +init_handoff(Stream, _StreamOpts, Conn, #{is_orphan := true, flags := Flags}) -> InitState = #{ stream => Stream, conn => Conn, @@ -45,7 +45,7 @@ init_handoff(Stream, StreamOpts, Conn, #{is_orphan := true, flags := Flags}) -> echo_stream => undefined, sent_bytes => 0 }, - ct:pal("init_handoff ~p", [{InitState, StreamOpts}]), + %ct:pal("init_handoff ~p", [{InitState, StreamOpts}]), {ok, InitState}. post_handoff(Stream, _PostData, State) -> diff --git a/test/quicer_listener_SUITE.erl b/test/quicer_listener_SUITE.erl index 26f1b72a..de7bcc81 100644 --- a/test/quicer_listener_SUITE.erl +++ b/test/quicer_listener_SUITE.erl @@ -100,7 +100,10 @@ end_per_group(_GroupName, _Config) -> %% @end %%-------------------------------------------------------------------- init_per_testcase(tc_listener_conf_reload_listen_on_neg, Config) -> - {skip, "MacOs is able to listen on port 1"}; + case os:type() of + {unix, darwin} -> {skip, "Not runnable on MacOS"}; + _ -> Config + end; init_per_testcase(_TestCase, Config) -> application:ensure_all_started(quicer), quicer_test_lib:cleanup_msquic(), @@ -318,21 +321,21 @@ tc_set_listener_opt(Config) -> {ok, L} = quicer:listen(Port, default_listen_opts(Config)), %% must start with 0 Val = <<0, 1, 2, 3, 4, 5>>, - ok = quicer:setopt(L, param_listener_cibir_id, Val), - {error, not_supported} = quicer:getopt(L, param_listener_cibir_id), + ok = quicer:setopt(L, cibir_id, Val), + {error, not_supported} = quicer:getopt(L, cibir_id), quicer:close_listener(L). tc_set_listener_opt_fail(Config) -> Port = select_port(), {ok, L} = quicer:listen(Port, default_listen_opts(Config)), - {error, _} = quicer:setopt(L, param_listener_cibir_id, <<1, 2, 3, 4, 5, 6>>), - {error, not_supported} = quicer:getopt(L, param_listener_cibir_id), + {error, _} = quicer:setopt(L, cibir_id, <<1, 2, 3, 4, 5, 6>>), + {error, not_supported} = quicer:getopt(L, cibir_id), quicer:close_listener(L). tc_get_listener_opt_addr(Config) -> Port = select_port(), {ok, L} = quicer:listen(Port, default_listen_opts(Config)), - {ok, {{0, 0, 0, 0}, Port}} = quicer:getopt(L, param_listener_local_address), + {ok, {{0, 0, 0, 0}, Port}} = quicer:getopt(L, local_address), quicer:close_listener(L). tc_get_listener_opt_stats(Config) -> @@ -342,7 +345,7 @@ tc_get_listener_opt_stats(Config) -> {"total_accepted_connection", _}, {"total_rejected_connection", _}, {"binding_recv_dropped_packets", _} - ]} = quicer:getopt(L, param_listener_stats), + ]} = quicer:getopt(L, stats), quicer:close_listener(L). tc_close_listener(_Config) -> @@ -542,11 +545,12 @@ tc_listener_conf_reload(Config) -> ), {ok, _} = quicer:send(Stream2, <<"ping_from_conn_2">>), - receive - {quic, new_stream, Stream2Remote, #{is_orphan := true}} -> - quicer:setopt(Stream2Remote, active, true), - ok - end, + Stream2Remote = + receive + {quic, new_stream, Stream2R, #{is_orphan := true}} -> + quicer:setopt(Stream2R, active, true), + Stream2R + end, receive {quic, <<"ping_from_conn_2">>, Stream2Remote, _} -> ok @@ -623,12 +627,12 @@ tc_listener_conf_reload_listen_on(Config) -> ), {ok, _} = quicer:send(Stream2, <<"ping_from_conn_2">>), - receive - {quic, new_stream, Stream2Remote, #{is_orphan := true}} -> - quicer:setopt(Stream2Remote, active, true), - ok - end, - + Stream2Remote = + receive + {quic, new_stream, Stream2R, #{is_orphan := true}} -> + quicer:setopt(Stream2R, active, true), + Stream2R + end, receive {quic, <<"ping_from_conn_2">>, Stream2Remote, _} -> ok after 2000 -> @@ -662,7 +666,7 @@ tc_listener_conf_reload_listen_on_neg(Config) -> Options = {ListenerOpts, ConnectionOpts, StreamOpts}, %% Given a QUIC connection between example client and example server - {ok, QuicApp} = quicer:spawn_listener(sample, Port, Options), + {ok, QuicApp} = quicer:spawn_listener(?FUNCTION_NAME, Port, Options), ClientConnOpts = default_conn_opts_verify(Config, ca), {ok, ClientConnPid} = example_client_connection:start_link( "localhost", @@ -705,11 +709,12 @@ tc_listener_conf_reload_listen_on_neg(Config) -> ), {ok, _} = quicer:send(Stream2, <<"ping_from_conn_2">>), - receive - {quic, new_stream, Stream2Remote, #{is_orphan := true}} -> - quicer:setopt(Stream2Remote, active, true), - ok - end, + Stream2Remote = + receive + {quic, new_stream, Stream2R, #{is_orphan := true}} -> + quicer:setopt(Stream2R, active, true), + Stream2R + end, receive {quic, <<"ping_from_conn_2">>, Stream2Remote, _} -> ok diff --git a/test/quicer_prop_gen.erl b/test/quicer_prop_gen.erl new file mode 100644 index 00000000..c7e47930 --- /dev/null +++ b/test/quicer_prop_gen.erl @@ -0,0 +1,337 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 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_prop_gen). + +-export([ + valid_client_conn_opts/0, + valid_server_listen_opts/0, + valid_stream_shutdown_flags/0, + valid_stream_start_flags/0, + valid_stream_handle/0, + valid_started_connection_handle/0, + valid_opened_connection_handle/0, + valid_connection_handle/0, + valid_listen_on/0, + valid_listen_opts/0, + valid_listen_handle/0, + valid_global_handle/0, + valid_reg_handle/0, + valid_handle/0, + valid_csend_stream_opts/0, + pid/0, + data/0, + quicer_send_flags/0, + latin1_string/0 +]). + +-include_lib("proper/include/proper.hrl"). +-include_lib("quicer/include/quicer_types.hrl"). + +-include("prop_quic_types.hrl"). + +valid_handle() -> + oneof([ + valid_connection_handle(), + valid_stream_handle(), + valid_listen_handle(), + valid_reg_handle(), + valid_global_handle() + ]). + +%% @doc pid of process that dies randomly within 0-1000(ms) +pid() -> + ?LET( + LiveTimeMs, + range(0, 1000), + spawn(fun() -> timer:sleep(LiveTimeMs) end) + ). + +data() -> + oneof([binary(), list(binary())]). + +quicer_send_flags() -> + ?LET( + Flags, + [send_flags()], + begin + lists:foldl( + fun(F, Acc) -> + Acc bor F + end, + 0, + Flags + ) + end + ). + +%% valid reg handle +valid_reg_handle() -> + ?SUCHTHAT( + Handle, + ?LET( + {Name, Profile}, + {reg_name(), registration_profile()}, + begin + case quicer_nif:new_registration(Name, Profile) of + {ok, Handle} -> + #prop_handle{ + type = reg, + name = Name, + handle = Handle, + destructor = fun() -> + quicer_nif:close_registration(Handle) + end + }; + {error, _} -> + error + end + end + ), + Handle =/= error + ). + +reg_name() -> + % latin1_string() + ?LET( + Rand, + integer(), + begin + "foo" ++ integer_to_list(Rand) + end + ). + +valid_global_handle() -> + ?LET(_H, integer(), #prop_handle{ + type = global, + handle = quic_global, + destructor = fun() -> ok end + }). + +valid_listen_handle() -> + ?SUCHTHAT( + Ret, + ?LET( + {On, Opts}, + {valid_listen_on(), valid_listen_opts()}, + begin + case quicer_nif:listen(On, maps:from_list(Opts)) of + {ok, Handle} -> + #prop_handle{ + type = listener, + name = "noname", + handle = Handle, + destructor = fun() -> + quicer_nif:close_listener(Handle) + end + }; + _E -> + ct:pal("listen failed: ~p", [_E]), + error + end + end + ), + Ret =/= error + ). + +valid_listen_opts() -> + ?LET( + Opts, + quicer_listen_opts(), + begin + lists:foldl( + fun proplists:delete/2, + Opts ++ valid_server_listen_opts(), + [ + password, + %% flaky per machine sysconf + stream_recv_buffer_default + ] + ) + end + ). + +valid_listen_on() -> + ?LET( + Port, + range(1025, 65536), + begin + case gen_udp:open(Port, [{reuseaddr, true}]) of + {ok, S} -> + ok = gen_udp:close(S), + Port; + _ -> + quicer_test_lib:select_free_port(quic) + end + end + ). + +%% @doc valid conn handle in different states (opened, started, closed) +valid_connection_handle() -> + oneof([ + valid_opened_connection_handle(), + valid_started_connection_handle() + ]). + +valid_opened_connection_handle() -> + ?LET( + _Rand, + integer(), + begin + {ok, Handle} = quicer_nif:open_connection(), + #prop_handle{ + type = conn, + name = "noname", + handle = Handle, + destructor = fun() -> + quicer_nif:async_shutdown_connection(Handle, 0, 0) + end + } + end + ). + +valid_started_connection_handle() -> + ensure_dummy_listener(?DUMMY_PORT), + ?LET( + _Rand, + integer(), + begin + {ok, Handle} = quicer_nif:async_connect( + "localhost", ?DUMMY_PORT, maps:from_list(valid_client_conn_opts()) + ), + #prop_handle{ + type = conn, + name = "noname", + handle = Handle, + destructor = fun() -> + quicer_nif:async_shutdown_connection(Handle, 0, 0) + end + } + end + ). + +valid_stream_handle() -> + ensure_dummy_listener(?DUMMY_PORT), + ?SUCHTHAT( + Conn, + ?LET( + _Rand, + integer(), + begin + {ok, Conn} = quicer_nif:async_connect( + "localhost", ?DUMMY_PORT, maps:from_list(valid_client_conn_opts()) + ), + receive + {quic, connected, Conn, _} -> + {ok, Stream} = quicer_nif:start_stream(Conn, #{active => 1}), + #prop_handle{ + type = stream, + name = "noname", + handle = Stream, + destructor = + fun() -> + quicer_nif:async_shutdown_connection(Conn, 0, 0) + end + } + after 100 -> + %% @FIXME + error + end + end + ), + Conn =/= error + ). + +valid_stream_start_flags() -> + ?valid_flags(stream_start_flag()). + +valid_stream_shutdown_flags() -> + ?valid_flags(stream_shutdown_flags()). + +latin1_string() -> ?SUCHTHAT(S, string(), io_lib:printable_latin1_list(S)). + +%% @doc Server listen opts must work +valid_server_listen_opts() -> + [ + {alpn, ["proper"]}, + {cacertfile, "./msquic/submodules/openssl/test/certs/rootCA.pem"}, + {certfile, "./msquic/submodules/openssl/test/certs/servercert.pem"}, + {keyfile, "./msquic/submodules/openssl/test/certs/serverkey.pem"} + ]. + +valid_client_conn_opts() -> + [ + {alpn, ["proper"]}, + {cacertfile, "./msquic/submodules/openssl/test/certs/rootCA.pem"}, + {certfile, "./msquic/submodules/openssl/test/certs/servercert.pem"}, + {keyfile, "./msquic/submodules/openssl/test/certs/serverkey.pem"} + ]. + +valid_csend_stream_opts() -> + ?LET( + Opts, + quicer_stream_opts(), + maps:without( + [start_flag], + maps:from_list([{active, true} | Opts]) + ) + ). + +-spec ensure_dummy_listener(non_neg_integer()) -> _. +ensure_dummy_listener(Port) -> + case is_pid(whereis(?dummy_listener)) of + false -> + spawn_dummy_listener(Port); + true -> + ok + end. + +spawn_dummy_listener(Port) -> + Parent = self(), + spawn(fun() -> + register(?dummy_listener, self()), + {ok, L} = quicer_nif:listen(Port, maps:from_list(valid_server_listen_opts())), + spawn_acceptors(L, 4), + Parent ! ready, + receive + finish -> ok + end + end), + receive + ready -> + ok + end. + +spawn_acceptors(_, 0) -> + ok; +spawn_acceptors(L, N) -> + spawn_link(fun() -> + acceptor_loop(L) + end), + spawn_acceptors(L, N - 1). + +acceptor_loop(L) -> + case quicer:accept(L, #{active => true}) of + {ok, Conn} -> + spawn(fun() -> + _ = quicer:handshake(Conn), + timer:sleep(100), + quicer:async_shutdown_connection(Conn, 0, 0) + end), + acceptor_loop(L); + _ -> + acceptor_loop(L) + end. diff --git a/test/quicer_snb_SUITE.erl b/test/quicer_snb_SUITE.erl index e2523c50..214230e7 100644 --- a/test/quicer_snb_SUITE.erl +++ b/test/quicer_snb_SUITE.erl @@ -1783,7 +1783,7 @@ tc_conn_resume_nst(Config) -> [{quic_event_mask, ?QUICER_CONNECTION_EVENT_MASK_NST} | default_conn_opts()], 5000 ), - {ok, HandshakeInfo} = quicer:getopt(Conn, param_tls_handshake_info, quic_tls), + {ok, HandshakeInfo} = quicer:getopt(Conn, handshake_info, quic_tls), {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), {ok, 4} = quicer:async_send(Stm, <<"ping">>), {ok, <<"ping">>} = quicer:recv(Stm, 4), @@ -1801,7 +1801,7 @@ tc_conn_resume_nst(Config) -> ), {ok, Stm2} = quicer:start_stream(ConnResumed, [{active, false}]), {ok, HandshakeInfo0RTT} = quicer:getopt( - ConnResumed, param_tls_handshake_info, quic_tls + ConnResumed, handshake_info, quic_tls ), ct:pal("handshake info:~n1RTT: ~p~n0RTT: ~p~n", [HandshakeInfo, HandshakeInfo0RTT]), ?assertEqual(HandshakeInfo, HandshakeInfo0RTT), @@ -2100,7 +2100,7 @@ tc_conn_resume_nst_async_2(Config) -> end, quicer:close_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 111), {ok, NewConn} = quicer:open_connection(), - ok = quicer:setopt(NewConn, param_conn_resumption_ticket, NST), + ok = quicer:setopt(NewConn, resumption_ticket, NST), {ok, ConnResumed} = quicer:async_connect("localhost", Port, [ {handle, NewConn} | default_conn_opts() ]), @@ -2196,8 +2196,8 @@ tc_conn_resume_nst_with_data(Config) -> end, quicer:close_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 111), {ok, NewConn} = quicer_nif:open_connection(), - ok = quicer:setopt(NewConn, param_conn_share_udp_binding, false), - ?assertEqual({ok, false}, quicer:getopt(NewConn, param_conn_share_udp_binding)), + ok = quicer:setopt(NewConn, share_udp_binding, false), + ?assertEqual({ok, false}, quicer:getopt(NewConn, share_udp_binding)), %% Send data over new stream in the resumed connection {ok, Stm2} = quicer:async_csend( diff --git a/tools/asan/bin/sanitizer-check b/tools/asan/bin/sanitizer-check index 071bf9be..ca6cbc59 100755 --- a/tools/asan/bin/sanitizer-check +++ b/tools/asan/bin/sanitizer-check @@ -51,6 +51,9 @@ if [ $# -eq 1 ]; then escript "$REBAR3" do ct --suite=test/quicer_SUITE --case="$tc"; done ;; + proper) + escript "$REBAR3" as test proper + ;; esac else escript "$REBAR3" do ct $@