spec followers finish last
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@7260 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
parent
ee328938aa
commit
8ccad0a22f
|
@ -370,17 +370,20 @@ typedef enum {
|
|||
SWITCH_RTP_FLAG_IO = (1 << 1),
|
||||
SWITCH_RTP_FLAG_USE_TIMER = (1 << 2),
|
||||
SWITCH_RTP_FLAG_TIMER_RECLOCK = (1 << 3),
|
||||
SWITCH_RTP_FLAG_SECURE = (1 << 4),
|
||||
SWITCH_RTP_FLAG_AUTOADJ = (1 << 5),
|
||||
SWITCH_RTP_FLAG_RAW_WRITE = (1 << 6),
|
||||
SWITCH_RTP_FLAG_GOOGLEHACK = (1 << 7),
|
||||
SWITCH_RTP_FLAG_VAD = (1 << 8),
|
||||
SWITCH_RTP_FLAG_BREAK = (1 << 9),
|
||||
SWITCH_RTP_FLAG_MINI = (1 << 10),
|
||||
SWITCH_RTP_FLAG_DATAWAIT = (1 << 11),
|
||||
SWITCH_RTP_FLAG_BUGGY_2833 = (1 << 12),
|
||||
SWITCH_RTP_FLAG_PASS_RFC2833 = (1 << 13),
|
||||
SWITCH_RTP_FLAG_AUTO_CNG = (1 << 14)
|
||||
SWITCH_RTP_FLAG_SECURE_SEND = (1 << 4),
|
||||
SWITCH_RTP_FLAG_SECURE_RECV = (1 << 5),
|
||||
SWITCH_RTP_FLAG_AUTOADJ = (1 << 6),
|
||||
SWITCH_RTP_FLAG_RAW_WRITE = (1 << 7),
|
||||
SWITCH_RTP_FLAG_GOOGLEHACK = (1 << 8),
|
||||
SWITCH_RTP_FLAG_VAD = (1 << 9),
|
||||
SWITCH_RTP_FLAG_BREAK = (1 << 10),
|
||||
SWITCH_RTP_FLAG_MINI = (1 << 11),
|
||||
SWITCH_RTP_FLAG_DATAWAIT = (1 << 12),
|
||||
SWITCH_RTP_FLAG_BUGGY_2833 = (1 << 13),
|
||||
SWITCH_RTP_FLAG_PASS_RFC2833 = (1 << 14),
|
||||
SWITCH_RTP_FLAG_AUTO_CNG = (1 << 15),
|
||||
SWITCH_RTP_FLAG_SECURE_SEND_RESET = (1 << 16),
|
||||
SWITCH_RTP_FLAG_SECURE_RECV_RESET = (1 << 17)
|
||||
} switch_rtp_flag_t;
|
||||
|
||||
/*!
|
||||
|
|
|
@ -82,15 +82,16 @@ void sofia_glue_set_local_sdp(private_object_t *tech_pvt, const char *ip, uint32
|
|||
tech_pvt->session_id++;
|
||||
|
||||
switch_snprintf(buf, sizeof(buf),
|
||||
"v=0\n"
|
||||
"o=FreeSWITCH %010u %010u IN IP4 %s\n"
|
||||
"s=FreeSWITCH\n"
|
||||
"c=IN IP4 %s\n" "t=0 0\n"
|
||||
"a=%s\n"
|
||||
"m=audio %d RTP/AVP", tech_pvt->owner_id, tech_pvt->session_id, ip, ip, sr, port);
|
||||
"v=0\n"
|
||||
"o=FreeSWITCH %010u %010u IN IP4 %s\n"
|
||||
"s=FreeSWITCH\n"
|
||||
"c=IN IP4 %s\n" "t=0 0\n"
|
||||
"a=%s\n"
|
||||
"m=audio %d RTP/%sAVP", tech_pvt->owner_id, tech_pvt->session_id, ip, ip, sr, port,
|
||||
(!switch_strlen_zero(tech_pvt->local_crypto_key) && switch_test_flag(tech_pvt, TFLAG_SECURE)) ? "S" : ""
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (tech_pvt->rm_encoding) {
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %d", tech_pvt->pt);
|
||||
} else if (tech_pvt->num_codecs) {
|
||||
|
@ -184,9 +185,38 @@ void sofia_glue_set_local_sdp(private_object_t *tech_pvt, const char *ip, uint32
|
|||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=ptime:%d\n", ptime);
|
||||
}
|
||||
|
||||
|
||||
if (!switch_strlen_zero(tech_pvt->local_crypto_key) && switch_test_flag(tech_pvt, TFLAG_SECURE)) {
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=crypto:%s\n", tech_pvt->local_crypto_key);
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=encryption:optional\n");
|
||||
//switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=encryption:optional\n");
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "m=audio %d RTP/AVP", port);
|
||||
|
||||
if (tech_pvt->rm_encoding) {
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %d", tech_pvt->pt);
|
||||
} else if (tech_pvt->num_codecs) {
|
||||
int i;
|
||||
int already_did[128] = { 0 };
|
||||
for (i = 0; i < tech_pvt->num_codecs; i++) {
|
||||
const switch_codec_implementation_t *imp = tech_pvt->codecs[i];
|
||||
|
||||
if (imp->codec_type != SWITCH_CODEC_TYPE_AUDIO) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (imp->ianacode < 128) {
|
||||
if (already_did[imp->ianacode]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
already_did[imp->ianacode] = 1;
|
||||
}
|
||||
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %d", imp->ianacode);
|
||||
}
|
||||
}
|
||||
|
||||
switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "\na=crypto:%s\n", tech_pvt->local_crypto_key);
|
||||
|
||||
}
|
||||
|
||||
if (switch_test_flag(tech_pvt, TFLAG_VIDEO) && tech_pvt->video_rm_encoding) {
|
||||
|
@ -1455,6 +1485,7 @@ uint8_t sofia_glue_negotiate_sdp(switch_core_session_t *session, sdp_session_t *
|
|||
switch_channel_t *channel = NULL;
|
||||
const char *val;
|
||||
const char *crypto = NULL;
|
||||
int got_crypto = 0;
|
||||
|
||||
tech_pvt = switch_core_session_get_private(session);
|
||||
switch_assert(tech_pvt != NULL);
|
||||
|
@ -1542,30 +1573,40 @@ uint8_t sofia_glue_negotiate_sdp(switch_core_session_t *session, sdp_session_t *
|
|||
|
||||
if (m->m_type == sdp_media_audio) {
|
||||
sdp_rtpmap_t *map;
|
||||
|
||||
|
||||
for (a = m->m_attributes; a; a = a->a_next) {
|
||||
if (!strcasecmp(a->a_name, "ptime") && a->a_value) {
|
||||
ptime = atoi(a->a_value);
|
||||
} else if (!strcasecmp(a->a_name, "crypto") && a->a_value) {
|
||||
} else if (!got_crypto && !strcasecmp(a->a_name, "crypto") && !switch_strlen_zero(a->a_value)) {
|
||||
crypto = a->a_value;
|
||||
int crypto_tag = atoi(crypto);
|
||||
|
||||
|
||||
if (tech_pvt->remote_crypto_key) {
|
||||
if (crypto_tag && crypto_tag == tech_pvt->crypto_tag) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Existing key is still valid.\n");
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Change Remote key to [%s]\n", crypto);
|
||||
tech_pvt->remote_crypto_key = switch_core_session_strdup(tech_pvt->session, crypto);
|
||||
tech_pvt->crypto_tag = crypto_tag;
|
||||
sofia_glue_add_crypto(tech_pvt, tech_pvt->remote_crypto_key, SWITCH_RTP_CRYPTO_RECV);
|
||||
switch_rtp_add_crypto_key(tech_pvt->rtp_session, SWITCH_RTP_CRYPTO_RECV, tech_pvt->crypto_tag,
|
||||
tech_pvt->crypto_type, tech_pvt->remote_raw_key, SWITCH_RTP_KEY_LEN);
|
||||
const char *a = switch_stristr("AES", tech_pvt->remote_crypto_key);
|
||||
const char *b = switch_stristr("AES", crypto);
|
||||
|
||||
if (a && b && strncasecmp(a, b, 23)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Change Remote key to [%s]\n", crypto);
|
||||
tech_pvt->remote_crypto_key = switch_core_session_strdup(tech_pvt->session, crypto);
|
||||
tech_pvt->crypto_tag = crypto_tag;
|
||||
sofia_glue_add_crypto(tech_pvt, tech_pvt->remote_crypto_key, SWITCH_RTP_CRYPTO_RECV);
|
||||
switch_rtp_add_crypto_key(tech_pvt->rtp_session, SWITCH_RTP_CRYPTO_RECV, tech_pvt->crypto_tag,
|
||||
tech_pvt->crypto_type, tech_pvt->remote_raw_key, SWITCH_RTP_KEY_LEN);
|
||||
got_crypto++;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Ignoring unacceptable key\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tech_pvt->remote_crypto_key = switch_core_session_strdup(tech_pvt->session, crypto);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set Remote Key [%s]\n", tech_pvt->remote_crypto_key);
|
||||
tech_pvt->crypto_tag = crypto_tag;
|
||||
|
||||
got_crypto++;
|
||||
|
||||
if (switch_strlen_zero(tech_pvt->local_crypto_key)) {
|
||||
if (switch_stristr(SWITCH_RTP_CRYPTO_KEY_32, crypto)) {
|
||||
switch_channel_set_variable(tech_pvt->channel, SOFIA_HAS_CRYPTO_VARIABLE, SWITCH_RTP_CRYPTO_KEY_32);
|
||||
|
@ -1577,6 +1618,16 @@ uint8_t sofia_glue_negotiate_sdp(switch_core_session_t *session, sdp_session_t *
|
|||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Crypto Setup Failed!.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (switch_rtp_ready(tech_pvt->rtp_session) &&
|
||||
!switch_strlen_zero(tech_pvt->local_crypto_key) && !switch_strlen_zero(tech_pvt->remote_crypto_key)) {
|
||||
switch_set_flag_locked(tech_pvt, TFLAG_SECURE);
|
||||
sofia_glue_set_local_sdp(tech_pvt, NULL, 0, NULL, 1);
|
||||
switch_rtp_add_crypto_key(tech_pvt->rtp_session, SWITCH_RTP_CRYPTO_RECV, tech_pvt->crypto_tag,
|
||||
tech_pvt->crypto_type, tech_pvt->remote_raw_key, SWITCH_RTP_KEY_LEN);
|
||||
switch_rtp_add_crypto_key(tech_pvt->rtp_session, SWITCH_RTP_CRYPTO_SEND, tech_pvt->crypto_tag,
|
||||
tech_pvt->crypto_type, tech_pvt->local_raw_key, SWITCH_RTP_KEY_LEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
110
src/switch_rtp.c
110
src/switch_rtp.c
|
@ -495,10 +495,15 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess
|
|||
switch_rtp_crypto_key_t *crypto_key;
|
||||
srtp_policy_t *policy;
|
||||
err_status_t stat;
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
|
||||
crypto_key = switch_core_alloc(rtp_session->pool, sizeof(*crypto_key));
|
||||
|
||||
policy = direction == SWITCH_RTP_CRYPTO_RECV ? &rtp_session->recv_policy : &rtp_session->send_policy;
|
||||
if (direction == SWITCH_RTP_CRYPTO_RECV) {
|
||||
policy = &rtp_session->recv_policy;
|
||||
} else {
|
||||
policy = &rtp_session->send_policy;
|
||||
}
|
||||
|
||||
|
||||
crypto_key->type = type;
|
||||
|
@ -509,7 +514,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess
|
|||
|
||||
|
||||
memset(policy, 0, sizeof(*policy));
|
||||
switch_set_flag_locked(rtp_session, SWITCH_RTP_FLAG_SECURE);
|
||||
|
||||
|
||||
switch(crypto_key->type) {
|
||||
case AES_CM_128_HMAC_SHA1_80:
|
||||
|
@ -532,21 +537,43 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess
|
|||
switch(direction) {
|
||||
case SWITCH_RTP_CRYPTO_RECV:
|
||||
policy->ssrc.type = ssrc_any_inbound;
|
||||
if ((stat = srtp_create(&rtp_session->recv_ctx, policy))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV)) {
|
||||
switch_set_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV_RESET);
|
||||
} else {
|
||||
if ((stat = srtp_create(&rtp_session->recv_ctx, policy))) {
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (status == SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Activating Secure RTP RECV\n");
|
||||
switch_set_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Activating Secure RTP RECV\n");
|
||||
break;
|
||||
case SWITCH_RTP_CRYPTO_SEND:
|
||||
policy->ssrc.type = ssrc_specific;
|
||||
policy->ssrc.value = rtp_session->ssrc;
|
||||
|
||||
if ((stat = srtp_create(&rtp_session->send_ctx, policy))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND)) {
|
||||
switch_set_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND_RESET);
|
||||
} else {
|
||||
if ((stat = srtp_create(&rtp_session->send_ctx, policy))) {
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (status == SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Activating Secure RTP SEND\n");
|
||||
switch_set_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Activating Secure RTP SEND\n");
|
||||
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
|
@ -774,9 +801,20 @@ SWITCH_DECLARE(void) switch_rtp_destroy(switch_rtp_t **rtp_session)
|
|||
switch_rtp_disable_vad(*rtp_session);
|
||||
}
|
||||
|
||||
if (switch_test_flag((*rtp_session), SWITCH_RTP_FLAG_SECURE)) {
|
||||
srtp_dealloc((*rtp_session)->recv_ctx);
|
||||
if (switch_test_flag((*rtp_session), SWITCH_RTP_FLAG_SECURE_SEND)) {
|
||||
switch_mutex_lock((*rtp_session)->flag_mutex);
|
||||
srtp_dealloc((*rtp_session)->send_ctx);
|
||||
(*rtp_session)->send_ctx = NULL;
|
||||
switch_clear_flag((*rtp_session), SWITCH_RTP_FLAG_SECURE_SEND);
|
||||
switch_mutex_unlock((*rtp_session)->flag_mutex);
|
||||
}
|
||||
|
||||
if (switch_test_flag((*rtp_session), SWITCH_RTP_FLAG_SECURE_RECV)) {
|
||||
switch_mutex_lock((*rtp_session)->flag_mutex);
|
||||
srtp_dealloc((*rtp_session)->recv_ctx);
|
||||
(*rtp_session)->recv_ctx = NULL;
|
||||
switch_clear_flag((*rtp_session), SWITCH_RTP_FLAG_SECURE_RECV);
|
||||
switch_mutex_unlock((*rtp_session)->flag_mutex);
|
||||
}
|
||||
|
||||
if ((*rtp_session)->timer.timer_interface) {
|
||||
|
@ -970,12 +1008,26 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (bytes && switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE)) {
|
||||
if (bytes && switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV)) {
|
||||
int sbytes = (int) bytes;
|
||||
err_status_t stat;
|
||||
err_status_t stat = 0;
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV_RESET)) {
|
||||
switch_clear_flag_locked(rtp_session, SWITCH_RTP_FLAG_SECURE_RECV_RESET);
|
||||
srtp_dealloc(rtp_session->recv_ctx);
|
||||
rtp_session->recv_ctx = NULL;
|
||||
if ((stat = srtp_create(&rtp_session->recv_ctx, &rtp_session->recv_policy))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error! RE-Activating Secure RTP RECV\n");
|
||||
return -1;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "RE-Activating Secure RTP RECV\n");
|
||||
rtp_session->srtp_errs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
stat = srtp_unprotect(rtp_session->recv_ctx, &rtp_session->recv_msg.header, &sbytes);
|
||||
|
||||
|
||||
if (stat && rtp_session->recv_msg.header.pt != rtp_session->te && rtp_session->recv_msg.header.pt != rtp_session->cng_pt) {
|
||||
if (++rtp_session->srtp_errs >= MAX_SRTP_ERRS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
|
||||
|
@ -1558,10 +1610,24 @@ static int rtp_common_write(switch_rtp_t *rtp_session,
|
|||
send_msg->header.seq = htons(++rtp_session->seq);
|
||||
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE)) {
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND)) {
|
||||
int sbytes = (int) bytes;
|
||||
err_status_t stat;
|
||||
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND_RESET)) {
|
||||
switch_clear_flag_locked(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND_RESET);
|
||||
srtp_dealloc(rtp_session->send_ctx);
|
||||
rtp_session->send_ctx = NULL;
|
||||
if ((stat = srtp_create(&rtp_session->send_ctx, &rtp_session->send_policy))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error! RE-Activating Secure RTP SEND\n");
|
||||
return -1;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "RE-Activating Secure RTP SEND\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stat = srtp_protect(rtp_session->send_ctx, &send_msg->header, &sbytes);
|
||||
if (stat) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error: srtp protection failed with code %d\n", stat);
|
||||
|
@ -1723,10 +1789,22 @@ SWITCH_DECLARE(int) switch_rtp_write_manual(switch_rtp_t *rtp_session,
|
|||
|
||||
bytes = rtp_header_len + datalen;
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE)) {
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND)) {
|
||||
int sbytes = (int) bytes;
|
||||
err_status_t stat;
|
||||
|
||||
if (switch_test_flag(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND_RESET)) {
|
||||
switch_clear_flag_locked(rtp_session, SWITCH_RTP_FLAG_SECURE_SEND_RESET);
|
||||
srtp_dealloc(rtp_session->send_ctx);
|
||||
rtp_session->send_ctx = NULL;
|
||||
if ((stat = srtp_create(&rtp_session->send_ctx, &rtp_session->send_policy))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error! RE-Activating Secure RTP SEND\n");
|
||||
return -1;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "RE-Activating Secure RTP SEND\n");
|
||||
}
|
||||
}
|
||||
|
||||
stat = srtp_protect(rtp_session->send_ctx, &rtp_session->write_msg.header, &sbytes);
|
||||
if (stat) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error: srtp protection failed with code %d\n", stat);
|
||||
|
|
Loading…
Reference in New Issue