diff --git a/c_src/quicer_config.c b/c_src/quicer_config.c index daa0bd2a..0839fc60 100644 --- a/c_src/quicer_config.c +++ b/c_src/quicer_config.c @@ -25,6 +25,11 @@ static ERL_NIF_TERM get_stream_opt(ErlNifEnv *env, QuicerStreamCTX *s_ctx, ERL_NIF_TERM optname, ERL_NIF_TERM elevel); +static ERL_NIF_TERM set_stream_opt(ErlNifEnv *env, + QuicerStreamCTX *s_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel); static ERL_NIF_TERM get_connection_opt(ErlNifEnv *env, QuicerConnCTX *c_ctx, @@ -35,23 +40,49 @@ static ERL_NIF_TERM get_listener_opt(ErlNifEnv *env, QuicerListenerCTX *l_ctx, ERL_NIF_TERM optname, ERL_NIF_TERM elevel); +static ERL_NIF_TERM set_listener_opt(ErlNifEnv *env, + QuicerListenerCTX *l_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel); static ERL_NIF_TERM get_config_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname); +static ERL_NIF_TERM set_config_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval); static ERL_NIF_TERM get_reg_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname); +static ERL_NIF_TERM set_reg_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval); static ERL_NIF_TERM get_tls_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname); +static ERL_NIF_TERM set_tls_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval); static ERL_NIF_TERM get_global_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname); +static ERL_NIF_TERM set_global_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval); static ERL_NIF_TERM get_level_param(ErlNifEnv *env, HQUIC Handle, ERL_NIF_TERM level, ERL_NIF_TERM eopt); +static ERL_NIF_TERM set_level_param(ErlNifEnv *env, + HQUIC Handle, + ERL_NIF_TERM level, + ERL_NIF_TERM eopt, + ERL_NIF_TERM optval); bool ReloadCertConfig(HQUIC Configuration, QUIC_CREDENTIAL_CONFIG_HELPER *Config) @@ -610,25 +641,76 @@ get_level_param(ErlNifEnv *env, } ERL_NIF_TERM -setopt3(ErlNifEnv *env, +set_level_param(ErlNifEnv *env, + HQUIC Handle, + ERL_NIF_TERM level, + ERL_NIF_TERM eopt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + if (IS_SAME_TERM(ATOM_QUIC_REGISTRATION, level)) + { + res = set_reg_opt(env, Handle, eopt, eval); + } + if (IS_SAME_TERM(ATOM_QUIC_CONFIGURATION, level)) + { + res = set_config_opt(env, Handle, eopt, eval); + } + if (IS_SAME_TERM(ATOM_QUIC_TLS, level)) + { + res = set_tls_opt(env, Handle, eopt, eval); + } + + return res; +} + +ERL_NIF_TERM +setopt4(ErlNifEnv *env, __unused_parm__ int argc, __unused_parm__ const ERL_NIF_TERM argv[]) { ERL_NIF_TERM ctx = argv[0]; ERL_NIF_TERM eopt = argv[1]; ERL_NIF_TERM evalue = argv[2]; + ERL_NIF_TERM elevel = argv[3]; HQUIC Handle = NULL; QUIC_PARAM_LEVEL Level; QUIC_STATUS status = QUIC_STATUS_SUCCESS; - - void *q_ctx; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + void *q_ctx = NULL; if (!enif_is_atom(env, eopt)) { return ERROR_TUPLE_2(ATOM_BADARG); } + if (IS_SAME_TERM(ATOM_QUIC_GLOBAL, ctx)) + { + res = set_global_opt(env, NULL, eopt, evalue); + } + if (enif_get_resource(env, ctx, ctx_stream_t, &q_ctx)) + { + res = set_stream_opt( + env, (QuicerStreamCTX *)q_ctx, eopt, evalue, elevel); + } + else if (enif_get_resource(env, ctx, ctx_connection_t, &q_ctx)) + { + res = set_connection_opt( + env, (QuicerConnCTX *)q_ctx, eopt, evalue, elevel); + } + else if (enif_get_resource(env, ctx, ctx_listener_t, &q_ctx)) + { + res = set_listener_opt( + env, (QuicerListenerCTX *)q_ctx, eopt, evalue, elevel); + } + else + { //@todo support GLOBAL, REGISTRATION and CONFIGURATION + return ERROR_TUPLE_2(ATOM_BADARG); + } + + // old start + return res; if (enif_get_resource(env, ctx, ctx_stream_t, &q_ctx)) { Handle = ((QuicerStreamCTX *)q_ctx)->Stream; @@ -1046,6 +1128,117 @@ get_stream_opt(ErlNifEnv *env, return res; } +static ERL_NIF_TERM +set_stream_opt(ErlNifEnv *env, + QuicerStreamCTX *s_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + QUIC_PARAM_LEVEL Level = QUIC_PARAM_LEVEL_STREAM; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + // Non Msquic Opts + if (IS_SAME_TERM(optname, ATOM_QUIC_STREAM_OPTS_ACTIVE)) + { + enif_mutex_lock(s_ctx->lock); + + if (ACCEPTOR_RECV_MODE_PASSIVE == s_ctx->owner->active + && s_ctx->is_buff_ready && s_ctx->TotalBufferLength > 0) + { + // trigger callback of event recv. + MsQuic->StreamReceiveComplete(s_ctx->Stream, 0); + MsQuic->StreamReceiveSetEnabled(s_ctx->Stream, TRUE); + } + if (!set_owner_recv_mode(s_ctx->owner, env, optval)) + { + enif_mutex_unlock(s_ctx->lock); + return ERROR_TUPLE_2(ATOM_PARAM_ERROR); + } + + enif_mutex_unlock(s_ctx->lock); + return ATOM_OK; + } + + // Msquic Opts ... + enif_mutex_lock(s_ctx->lock); + if (s_ctx->is_closed) + { + enif_mutex_unlock(s_ctx->lock); + return ERROR_TUPLE_2(ATOM_CLOSED); + } + enif_keep_resource(s_ctx); + enif_mutex_unlock(s_ctx->lock); + + if (!IS_SAME_TERM(ATOM_FALSE, elevel)) + { + res = set_level_param(env, s_ctx->Stream, optname, optval, elevel); + goto Exit; + } + + else if (ATOM_QUIC_PARAM_STREAM_ID == optname) + { + uint64_t stream_id = 0; + Param = QUIC_PARAM_STREAM_ID; + Level = QUIC_PARAM_LEVEL_STREAM; + BufferLength = sizeof(uint64_t); + Buffer = &stream_id; + } + else if (ATOM_QUIC_STREAM_OPTS_ACTIVE == optname) + { + if (ACCEPTOR_RECV_MODE_PASSIVE == s_ctx->owner->active) + { + res = SUCCESS(ATOM_FALSE); + } + else + { + res = SUCCESS(ATOM_TRUE); + } + goto Exit; + } + else if (ATOM_QUIC_PARAM_STREAM_0RTT_LENGTH == optname) + { + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (ATOM_QUIC_PARAM_STREAM_IDEAL_SEND_BUFFER_SIZE == optname) + { + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (s_ctx->c_ctx) + { + res = set_connection_opt(env, s_ctx->c_ctx, optname, optval, elevel); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + status = MsQuic->SetParam(s_ctx->Stream, Level, Param, BufferLength, Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } + +Exit: + enif_release_resource(s_ctx); + return res; +} + static ERL_NIF_TERM get_connection_opt(ErlNifEnv *env, QuicerConnCTX *c_ctx, @@ -1265,70 +1458,370 @@ get_connection_opt(ErlNifEnv *env, return res; } -static ERL_NIF_TERM -get_listener_opt(ErlNifEnv *env, - QuicerListenerCTX *l_ctx, - ERL_NIF_TERM optname, - ERL_NIF_TERM elevel) +ERL_NIF_TERM +set_connection_opt(ErlNifEnv *env, + QuicerConnCTX *c_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel) { QUIC_STATUS status = QUIC_STATUS_SUCCESS; void *Buffer = NULL; + bool isMalloc = FALSE; uint32_t BufferLength = 0; uint32_t Param = 0; + QUIC_PARAM_LEVEL Level; ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; - if (!l_ctx) - { - return ERROR_TUPLE_2(ATOM_BADARG); - } - - enif_mutex_lock(l_ctx->lock); - if (l_ctx->is_closed) + enif_mutex_lock(c_ctx->lock); + if (c_ctx->is_closed) { - enif_mutex_unlock(l_ctx->lock); + enif_mutex_unlock(c_ctx->lock); return ERROR_TUPLE_2(ATOM_CLOSED); } - enif_keep_resource(l_ctx); - enif_mutex_unlock(l_ctx->lock); + enif_keep_resource(c_ctx); + enif_mutex_unlock(c_ctx->lock); if (!IS_SAME_TERM(ATOM_FALSE, elevel)) { - res = get_level_param(env, l_ctx->Listener, optname, elevel); + res = set_level_param(env, c_ctx->Connection, optname, optval, elevel); goto Exit; } - else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_LOCAL_ADDRESS)) + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_QUIC_VERSION)) + { + Param = QUIC_PARAM_CONN_QUIC_VERSION; + Level = QUIC_PARAM_LEVEL_CONNECTION; + // QUIC_CONNECTION.stats.QuicVersion + BufferLength = sizeof(u_int32_t); + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS)) + { + QUIC_ADDR addr; + if (!parse_listen_on(env, optval, &addr)) + { + return ERROR_TUPLE_2(ATOM_BADARG); + } + + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_LOCAL_ADDRESS; + BufferLength = sizeof(QUIC_ADDR); + Buffer = &addr; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_REMOTE_ADDRESS)) + { + QUIC_ADDR addr; + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_REMOTE_ADDRESS; + BufferLength = sizeof(QUIC_ADDR); + Buffer = &addr; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_IDEAL_PROCESSOR)) { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_IDEAL_PROCESSOR; // @TODO - Param = QUIC_PARAM_LISTENER_LOCAL_ADDRESS; res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); goto Exit; } - else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_STATS)) + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_SETTINGS)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_SETTINGS; + BufferLength = sizeof(QUIC_SETTINGS); + QUIC_SETTINGS Settings = { 0 }; + if (!create_settings(env, &optval, &Settings)) + { + res = ERROR_TUPLE_2(ATOM_BADARG); + goto Exit; + } + Buffer = &Settings; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_STATISTICS)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_STATISTICS; + BufferLength = sizeof(QUIC_STATISTICS); + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_STATISTICS_PLAT)) { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_STATISTICS_PLAT; // @TODO - Param = QUIC_PARAM_LISTENER_STATS; res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); goto Exit; } - else + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_SHARE_UDP_BINDING)) { - res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_SHARE_UDP_BINDING; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); goto Exit; } - - assert(Param); - status = MsQuic->GetParam(l_ctx->Listener, - QUIC_PARAM_LEVEL_LISTENER, - Param, - &BufferLength, - Buffer); - - if (QUIC_SUCCEEDED(status)) + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT)) { - res = encode_parm_to_eterm( - env, QUIC_PARAM_LEVEL_LISTENER, Param, BufferLength, Buffer); + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; } - else + else if (IS_SAME_TERM(optname, + ATOM_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_MAX_STREAM_IDS)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_MAX_STREAM_IDS; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_CLOSE_REASON_PHRASE; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, + ATOM_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, + ATOM_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + /* + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + */ + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_RESUMPTION_TICKET)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_RESUMPTION_TICKET; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONN_LOCAL_INTERFACE)) + { + Level = QUIC_PARAM_LEVEL_CONNECTION; + Param = QUIC_PARAM_CONN_LOCAL_INTERFACE; + // @TODO + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (c_ctx->l_ctx) + { + res = set_listener_opt(env, c_ctx->l_ctx, optname, optval, elevel); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + if (!Buffer && !isMalloc) + { // when Buffer is not initialized. + Buffer = CXPLAT_ALLOC_NONPAGED(BufferLength, QUICER_OPT_BUFF); + if (!Buffer) + { + goto Exit; + } + isMalloc = TRUE; + } + + status = MsQuic->SetParam( + c_ctx->Connection, Level, Param, BufferLength, Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } + +Exit: + enif_release_resource(c_ctx); + return res; +} + +static ERL_NIF_TERM +get_listener_opt(ErlNifEnv *env, + QuicerListenerCTX *l_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM elevel) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (!l_ctx) + { + return ERROR_TUPLE_2(ATOM_BADARG); + } + + enif_mutex_lock(l_ctx->lock); + if (l_ctx->is_closed) + { + enif_mutex_unlock(l_ctx->lock); + return ERROR_TUPLE_2(ATOM_CLOSED); + } + enif_keep_resource(l_ctx); + enif_mutex_unlock(l_ctx->lock); + + if (!IS_SAME_TERM(ATOM_FALSE, elevel)) + { + res = get_level_param(env, l_ctx->Listener, optname, elevel); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_LOCAL_ADDRESS)) + { + // @TODO + Param = QUIC_PARAM_LISTENER_LOCAL_ADDRESS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_STATS)) + { + // @TODO + Param = QUIC_PARAM_LISTENER_STATS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->GetParam(l_ctx->Listener, + QUIC_PARAM_LEVEL_LISTENER, + Param, + &BufferLength, + Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = encode_parm_to_eterm( + env, QUIC_PARAM_LEVEL_LISTENER, Param, BufferLength, Buffer); + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } +Exit: + enif_release_resource(l_ctx); + return res; +} + +static ERL_NIF_TERM +set_listener_opt(ErlNifEnv *env, + QuicerListenerCTX *l_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (!l_ctx) + { + return ERROR_TUPLE_2(ATOM_BADARG); + } + + enif_mutex_lock(l_ctx->lock); + if (l_ctx->is_closed) + { + enif_mutex_unlock(l_ctx->lock); + return ERROR_TUPLE_2(ATOM_CLOSED); + } + enif_keep_resource(l_ctx); + enif_mutex_unlock(l_ctx->lock); + + if (!IS_SAME_TERM(ATOM_FALSE, elevel)) + { + res = set_level_param(env, l_ctx->Listener, optname, optval, elevel); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_LOCAL_ADDRESS)) + { + // @TODO + Param = QUIC_PARAM_LISTENER_LOCAL_ADDRESS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_LISTENER_STATS)) + { + // @TODO + Param = QUIC_PARAM_LISTENER_STATS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->SetParam( + l_ctx->Listener, QUIC_PARAM_LEVEL_LISTENER, Param, BufferLength, Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else { res = ERROR_TUPLE_2(ATOM_STATUS(status)); } @@ -1382,6 +1875,53 @@ get_tls_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) return res; } +static ERL_NIF_TERM +set_tls_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + __unused_parm__ ERL_NIF_TERM optval) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_TLS_HANDSHAKE_INFO)) + { + // @TODO + Param = QUIC_PARAM_TLS_HANDSHAKE_INFO; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_TLS_NEGOTIATED_ALPN)) + { + // @TODO + Param = QUIC_PARAM_TLS_NEGOTIATED_ALPN; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->SetParam( + Hanlder, QUIC_PARAM_LEVEL_TLS, Param, BufferLength, Buffer); + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } +Exit: + return res; +} + static ERL_NIF_TERM get_global_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) { @@ -1456,6 +1996,87 @@ get_global_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) return res; } +static ERL_NIF_TERM +set_global_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_RETRY_MEMORY_PERCENT)) + { + Param = QUIC_PARAM_GLOBAL_RETRY_MEMORY_PERCENT; + uint32_t percent = 0; + BufferLength = sizeof(uint32_t); + if (!enif_get_uint(env, optval, &percent) || percent > UINT16_MAX) + { + res = ERROR_TUPLE_2(ATOM_BADARG); + goto Exit; + } + Buffer = &percent; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_SUPPORTED_VERSIONS)) + { + // @TODO + Param = QUIC_PARAM_GLOBAL_SUPPORTED_VERSIONS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE)) + { + // @TODO + Param = QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_PERF_COUNTERS)) + { + // @TODO + Param = QUIC_PARAM_GLOBAL_PERF_COUNTERS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_SETTINGS)) + { + // @TODO + Param = QUIC_PARAM_GLOBAL_SETTINGS; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_GLOBAL_VERSION)) + { + // @TODO + Param = QUIC_PARAM_GLOBAL_VERSION; + res = ERROR_TUPLE_2(ATOM_STATUS(QUIC_STATUS_NOT_SUPPORTED)); + goto Exit; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->SetParam( + Hanlder, QUIC_PARAM_LEVEL_GLOBAL, Param, BufferLength, Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } +Exit: + return res; +} + static ERL_NIF_TERM get_config_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) { @@ -1495,6 +2116,51 @@ get_config_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) return res; } +static ERL_NIF_TERM +set_config_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_CONFIGURATION_SETTINGS)) + { + Param = QUIC_PARAM_CONFIGURATION_SETTINGS; + QUIC_SETTINGS Settings = { 0 }; + if (!create_settings(env, &optval, &Settings)) + { + return ERROR_TUPLE_2(ATOM_BADARG); + } + BufferLength = sizeof(Settings); + Buffer = &Settings; + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->SetParam( + Hanlder, QUIC_PARAM_LEVEL_CONFIGURATION, Param, BufferLength, Buffer); + + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } +Exit: + return res; +} + static ERL_NIF_TERM get_reg_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) { @@ -1532,3 +2198,45 @@ get_reg_opt(ErlNifEnv *env, HQUIC Hanlder, ERL_NIF_TERM optname) Exit: return res; } + +static ERL_NIF_TERM +set_reg_opt(ErlNifEnv *env, + HQUIC Hanlder, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval) +{ + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + void *Buffer = NULL; + uint32_t BufferLength = 0; + uint32_t Param = 0; + ERL_NIF_TERM res = ATOM_ERROR_NOT_FOUND; + + if (IS_SAME_TERM(optname, ATOM_QUIC_PARAM_REGISTRATION_CID_PREFIX)) + { + Param = QUIC_PARAM_REGISTRATION_CID_PREFIX; + if (!enif_get_string(env, optval, Buffer, BufferLength, ERL_NIF_LATIN1)) + { + res = ERROR_TUPLE_2(ATOM_BADARG); + goto Exit; + } + } + else + { + res = ERROR_TUPLE_2(ATOM_PARAM_ERROR); + goto Exit; + } + + assert(Param); + status = MsQuic->SetParam( + Hanlder, QUIC_PARAM_LEVEL_REGISTRATION, Param, BufferLength, Buffer); + if (QUIC_SUCCEEDED(status)) + { + res = ATOM_OK; + } + else + { + res = ERROR_TUPLE_2(ATOM_STATUS(status)); + } +Exit: + return res; +} diff --git a/c_src/quicer_config.h b/c_src/quicer_config.h index a14ce914..a4c242d5 100644 --- a/c_src/quicer_config.h +++ b/c_src/quicer_config.h @@ -56,7 +56,7 @@ bool get_uint64_from_map(ErlNifEnv *env, uint64_t *value); ERL_NIF_TERM getopt3(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); -ERL_NIF_TERM setopt3(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM setopt4(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); bool create_settings(ErlNifEnv *env, const ERL_NIF_TERM *emap, @@ -65,4 +65,10 @@ bool create_settings(ErlNifEnv *env, bool parse_listen_on(ErlNifEnv *env, ERL_NIF_TERM elisten_on, QUIC_ADDR *Address); +ERL_NIF_TERM set_connection_opt(ErlNifEnv *env, + QuicerConnCTX *c_ctx, + ERL_NIF_TERM optname, + ERL_NIF_TERM optval, + ERL_NIF_TERM elevel); + #endif // __QUICER_CONFIG_H_ diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index e289779b..89c882ff 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -310,6 +310,7 @@ ServerConnectionCallback(HQUIC Connection, acc = c_ctx->owner; acc_pid = &(acc->Pid); + // for fast connect: if (!(acc && enif_is_process_alive(c_ctx->env, acc_pid))) { acc = AcceptorDequeue( @@ -323,6 +324,12 @@ ServerConnectionCallback(HQUIC Connection, } assert(acc); + + if (!acc->fast_conn) + { + enif_release_resource(c_ctx); + } + // A monitor is automatically removed when it triggers or when the // resource is deallocated. enif_monitor_process(NULL, c_ctx, acc_pid, &c_ctx->owner_mon); @@ -381,6 +388,12 @@ ServerConnectionCallback(HQUIC Connection, TP_CB_3(shutdown_complete, Connection, Event->SHUTDOWN_COMPLETE.AppCloseInProgress); + + if (!c_ctx->owner->fast_conn + && !Event->SHUTDOWN_COMPLETE.HandshakeCompleted) + { + enif_release_resource(c_ctx); + } report = enif_make_tuple3( env, ATOM_QUIC, ATOM_CLOSED, enif_make_resource(env, c_ctx)); @@ -546,6 +559,22 @@ async_connect3(ErlNifEnv *env, } } + ERL_NIF_TERM evalue; + if (enif_get_map_value( + env, eoptions, ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, &evalue)) + { + if (!IS_SAME_TERM(ATOM_OK, + set_connection_opt(env, + c_ctx, + ATOM_QUIC_PARAM_CONN_LOCAL_ADDRESS, + evalue, + ATOM_FALSE))) + { + destroy_c_ctx(c_ctx); + return ERROR_TUPLE_2(ATOM_CONN_OPEN_ERROR); + } + } + if (QUIC_FAILED(Status = MsQuic->ConnectionStart(c_ctx->Connection, c_ctx->Configuration, QUIC_ADDRESS_FAMILY_UNSPEC, @@ -743,9 +772,13 @@ continue_connection_handshake(QuicerConnCTX *c_ctx) return QUIC_STATUS_INTERNAL_ERROR; } + // and releases resource in callback + enif_keep_resource(c_ctx); + if (QUIC_FAILED(Status = MsQuic->ConnectionSetConfiguration( c_ctx->Connection, c_ctx->l_ctx->Configuration))) { + enif_release_resource(c_ctx); return Status; } @@ -756,6 +789,7 @@ continue_connection_handshake(QuicerConnCTX *c_ctx) sizeof(QUIC_SETTINGS), &c_ctx->owner->Settings))) { + enif_release_resource(c_ctx); return Status; } return Status; diff --git a/c_src/quicer_nif.c b/c_src/quicer_nif.c index 8622356b..2873e111 100644 --- a/c_src/quicer_nif.c +++ b/c_src/quicer_nif.c @@ -1000,7 +1000,7 @@ static ErlNifFunc nif_funcs[] = { { "async_close_stream", 3, close_stream3, 0}, { "sockname", 1, sockname1, 0}, { "getopt", 3, getopt3, 0}, - { "setopt", 3, setopt3, 0}, + { "setopt", 4, setopt4, 0}, { "controlling_process", 2, controlling_process, 0}, /* for DEBUG */ { "get_conn_rid", 1, get_conn_rid1, 1}, diff --git a/docs/todo.org b/docs/todo.org index 1ce8cb6f..5b4eef25 100644 --- a/docs/todo.org +++ b/docs/todo.org @@ -5,6 +5,7 @@ * Features List Feature todo list, priority descending ** add safety check for closed handlers +check the is_closed flag or delay the close in ctx dealloc callback? ** Stream send iolist diff --git a/src/quicer.erl b/src/quicer.erl index 7ce0518d..81ee91c3 100644 --- a/src/quicer.erl +++ b/src/quicer.erl @@ -411,7 +411,7 @@ getopt(Handle, Opt, Optlevel) -> setopt(Handle, param_conn_settings, Value) when is_list(Value) -> setopt(Handle, param_conn_settings, maps:from_list(Value)); setopt(Handle, Opt, Value) -> - quicer_nif:setopt(Handle, Opt, Value). + quicer_nif:setopt(Handle, Opt, Value, false). -spec get_stream_id(Stream::stream_handler()) -> {ok, integer()} | {error, any()}. diff --git a/src/quicer_nif.erl b/src/quicer_nif.erl index 03c96f69..79af7eb9 100644 --- a/src/quicer_nif.erl +++ b/src/quicer_nif.erl @@ -31,7 +31,7 @@ , async_close_stream/3 , sockname/1 , getopt/3 - , setopt/3 + , setopt/4 , controlling_process/2 ]). @@ -164,11 +164,11 @@ sockname(_Conn) -> getopt(_Handle, _Optname, _IsRaw) -> erlang:nif_error(nif_library_not_loaded). --spec setopt(handler(), optname(), any()) -> +-spec setopt(handler(), optname(), any(), optlevel()) -> ok | {error, badarg | param_error | internal_error | not_enough_mem} | {error, atom_reason()}. -setopt(_Handle, _Opt, _Value) -> +setopt(_Handle, _Opt, _Value, _Level) -> erlang:nif_error(nif_library_not_loaded). -spec get_conn_rid(connection_handler()) -> diff --git a/test/quicer_SUITE.erl b/test/quicer_SUITE.erl index 70054c95..c87504b9 100644 --- a/test/quicer_SUITE.erl +++ b/test/quicer_SUITE.erl @@ -49,6 +49,7 @@ , tc_conn_basic_slow_start/1 , tc_conn_double_close/1 , tc_conn_other_port/1 + , tc_conn_with_localaddr/1 , tc_conn_controlling_process/1 , tc_stream_client_init/1 @@ -72,7 +73,11 @@ , tc_getopt/1 , tc_getopt_stream_active/1 , tc_setopt/1 - , tc_setopt_conn_local_addr/1 + + %% @TODO following two tcs are failing due to: + % https://github.com/microsoft/msquic/issues/2033 + % , tc_setopt_conn_local_addr/1 + % , tc_setopt_conn_local_addr_in_use/1 , tc_strm_opt_active_n/1 , tc_strm_opt_active_once/1 , tc_strm_opt_active_1/1 @@ -369,6 +374,26 @@ tc_conn_other_port(Config)-> ct:fail("timeout") end. +tc_conn_with_localaddr(Config)-> + Port = 5568, + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> simple_conn_server(Owner, Config, Port) end), + + {ok, CPort0} = gen_udp:open(0, [{ip, {127, 0, 0, 1}}]), + {ok, {{127, 0, 0, 1}, PortX}} = inet:sockname(CPort0), + ok = gen_udp:close(CPort0), + receive + listener_ready -> + {ok, Conn} = quicer:connect("127.0.0.1", Port, [{param_conn_local_address, "127.0.0.1:" ++ integer_to_list(PortX)} + | default_conn_opts()], 5000), + ?assertEqual({ok, {{127,0,0,1}, PortX}}, quicer:sockname(Conn)), + ok = quicer:close_connection(Conn), + SPid ! done, + ok = ensure_server_exit_normal(Ref) + after 1000 -> + ct:fail("timeout") + end. + tc_stream_client_init(Config) -> Port = 4568, Owner = self(), @@ -862,6 +887,7 @@ tc_getstat_closed(Config) -> {ok, 4} = quicer:send(Stm, <<"ping">>), receive {quic, _, _, _,_,_} -> ok end, ok = quicer:close_stream(Stm), + ok = quicer:close_connection(Conn), case quicer:getstat(Conn, [send_cnt, recv_oct, send_pend]) of {error,invalid_parameter} -> ok; {error,invalid_state} -> ok; @@ -1045,8 +1071,13 @@ tc_setopt_conn_local_addr(Config) -> %% change local addr with a new port 5060 ?assertEqual(ok, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:50600")), %% sleep is needed to finish migration at protocol level - timer:sleep(200), - ?assertEqual({ok, {{127,0,0,1}, 50600}}, quicer:sockname(Stm0)), + retry_with(fun() -> + timer:sleep(100), + case quicer:sockname(Stm0) of + {ok, {{127,0,0,1}, 50600}} -> true; + {ok, Other} -> {false, Other} + end + end, 20, "addr migration failed"), {ok, 5} = quicer:send(Stm0, <<"ping2">>), receive {quic, <<"ping2">>, Stm0, _, _, _} -> @@ -1061,6 +1092,52 @@ tc_setopt_conn_local_addr(Config) -> SPid ! done, ensure_server_exit_normal(Ref). +tc_setopt_conn_local_addr_in_use(Config) -> + Port = 4578, + Owner = self(), + {SPid, Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(), 5000), + {ok, Stm0} = quicer:start_stream(Conn, [{active, true}]), + {ok, 5} = quicer:send(Stm0, <<"ping1">>), + receive + {quic, <<"ping1">>, Stm0, _, _, _} -> + ok + after 1000 -> + ct:fail("recv ping1 timeout") + end, + {ok, OldAddr} = quicer:sockname(Stm0), + %% change local addr with a new random port (0) + ?assertEqual(ok, quicer:setopt(Conn, param_conn_local_address, "127.0.0.1:0")), + %% sleep is needed to finish migration at protocol level + timer:sleep(50), + {ok, NewAddr} = quicer:sockname(Stm0), + ?assertNotEqual(OldAddr, NewAddr), + ?assertNotEqual({ok, {{127,0,0,1}, 50600}}, NewAddr), + ?assertNotEqual({ok, {{127,0,0,1}, 50600}}, OldAddr), + + %% Occupy 50600 + {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")), + + %gen_udp:close(ESocket), + + %% sleep is needed to finish migration at protocol level + ct:pal("send after migration failed"), + {ok, 5} = quicer:send(Stm0, <<"ping2">>), + receive + {quic, <<"ping2">>, Stm0, _, _, _} -> + ok + after 1000 -> + ct:fail("recv ping2 timeout") + end, + %% check with server if peer addr is correct. + SPid ! {peer_addr, self()}, + receive {peer_addr, Peer} -> ok end, + ?assertEqual({ok, NewAddr}, Peer), + SPid ! done, + ensure_server_exit_normal(Ref). + tc_app_echo_server(Config) -> Port = 8888, application:ensure_all_started(quicer), @@ -1451,6 +1528,17 @@ active_recv(Stream, Len, BinList) -> end end. +retry_with(_Fun, 0, ErrorInfo) -> + ct:fail(ErrorInfo); +retry_with(Fun, Retry, ErrorInfo) -> + case Fun() of + true -> + ok; + false -> + retry_with(Fun, Retry - 1, ErrorInfo); + {false, NewErrorInfo} -> + retry_with(Fun, Retry - 1, NewErrorInfo) + end. %%%_* Emacs ==================================================================== %%% Local Variables: %%% allout-layout: t