From f28e428a320e3c9b62371fe586a11d17ba55ce51 Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Tue, 28 Oct 2025 18:08:23 +0200 Subject: [PATCH 1/9] publish events support --- src/plugins/janus_sip.c | 270 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 269 insertions(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index fb8d57f067..c5b72c1b5d 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -895,6 +895,16 @@ static struct janus_json_parameter sipmessage_parameters[] = { {"headers", JSON_OBJECT, 0}, {"call_id", JANUS_JSON_STRING, 0} }; +static struct janus_json_parameter publish_parameters[] = { + {"to", JSON_STRING, 0}, + {"event", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, + {"content_type", JSON_STRING, 0}, + {"content", JSON_STRING, 0}, + {"publish_ttl", JANUS_JSON_INTEGER, 0}, + {"etag", JSON_STRING, 0}, + {"headers", JSON_OBJECT, 0}, + {"call_id", JANUS_JSON_STRING, 0} +}; static struct janus_json_parameter keyframe_parameters[] = { {"user", JANUS_JSON_BOOL, 0}, {"peer", JANUS_JSON_BOOL, 0} @@ -915,6 +925,8 @@ static char *user_agent; static int register_ttl = JANUS_DEFAULT_REGISTER_TTL; #define JANUS_DEFAULT_SUBSCRIBE_TTL 3600 static int subscribe_ttl = JANUS_DEFAULT_SUBSCRIBE_TTL; +#define JANUS_DEFAULT_PUBLISH_TTL 3600 +static int publish_ttl = JANUS_DEFAULT_PUBLISH_TTL; static uint16_t rtp_range_min = 10000; static uint16_t rtp_range_max = 60000; static int dscp_audio_rtp = 0; @@ -1022,6 +1034,7 @@ struct ssip_s { nua_handle_t *s_nh_r, *s_nh_i, *s_nh_m; char *contact_header; /* Only needed for Sofia SIP >= 1.13 */ GHashTable *subscriptions; + GHashTable *publishers; janus_mutex smutex; struct janus_sip_session *session; }; @@ -3616,6 +3629,168 @@ static void *janus_sip_handler(void *data) { SIPTAG_EXPIRES_STR("0"), TAG_END()); result = json_object(); json_object_set_new(result, "event", json_string("unsubscribing")); + } else if(!strcasecmp(request_text, "publish")) { + /* Send a SIP PUBLISH request for an event package */ + JANUS_VALIDATE_JSON_OBJECT(root, publish_parameters, + error_code, error_cause, TRUE, + JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto error; + if(session->account.registration_status != janus_sip_registration_status_registered && + session->account.registration_status != janus_sip_registration_status_disabled) { + JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n"); + error_code = JANUS_SIP_ERROR_WRONG_STATE; + g_snprintf(error_cause, 512, "Wrong state (not registered)"); + goto error; + } + const char *to = json_string_value(json_object_get(root, "to")); + if(to == NULL) + to = session->account.identity; + const char *event_type = json_string_value(json_object_get(root, "event")); + /* TTL */ + int ttl = publish_ttl; + json_t *pub_ttl = json_object_get(root, "publish_ttl"); + if(pub_ttl && json_is_integer(pub_ttl)) + ttl = json_integer_value(pub_ttl); + if(ttl <= 0) + ttl = JANUS_DEFAULT_PUBLISH_TTL; + char ttl_text[20]; + g_snprintf(ttl_text, sizeof(ttl_text), "%d", ttl); + + /* Optional ETag for refresh/modify (If-Match) */ + const char *etag = json_string_value(json_object_get(root, "etag")); + + /* Take call-id from request, if it exists */ + const char *callid = NULL; + json_t *request_callid = json_object_get(root, "call_id"); + if(request_callid) + callid = json_string_value(request_callid); + + /* Create a hash key for the publishers table */ + const char *hashkey = event_type; + if(callid) + hashkey = callid; + + /* If call-id does not exist in request, create a random one */ + if(callid == NULL) { + JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n"); + callid = g_malloc0(24); + janus_sip_random_string(24, callid); + } + + /* Prepare or reuse per-event handle (publishers) */ + nua_handle_t *nh = NULL; + janus_mutex_lock(&session->stack->smutex); + if(session->stack->publishers != NULL) + nh = g_hash_table_lookup(session->stack->publishers, (char *)hashkey); + if(nh == NULL) { + /* Create a handle in the appropriate NUA */ + nua_t *use_nua = NULL; + ssip_t *use_stack = session->stack; + if(session->helper && session->master && session->master->stack) + use_stack = session->master->stack; + if(use_stack->s_nua == NULL) { + janus_mutex_unlock(&session->stack->smutex); + JANUS_LOG(LOG_ERR, "NUA destroyed while publishing?\n"); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } + use_nua = use_stack->s_nua; + nh = nua_handle(use_nua, session, TAG_END()); + if(session->stack->publishers == NULL) { + /* Create table for mapping publishers too */ + session->stack->publishers = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)g_free, (GDestroyNotify)nua_handle_destroy); + } + g_hash_table_insert(session->stack->publishers, g_strdup(hashkey), nh); + } + janus_mutex_unlock(&session->stack->smutex); + + char custom_headers[2048]; + janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); + char *contact_header = janus_sip_session_contact_header_retrieve(session); + char *proxy = session->helper && session->master ? + session->master->account.outbound_proxy : session->account.outbound_proxy; + const char *content_type = NULL; + json_t *content_type_text = json_object_get(root, "content_type"); + if(content_type_text && json_is_string(content_type_text)) + content_type = json_string_value(content_type_text); + const char *msg_content = NULL; + json_t *msg_content_text = json_object_get(root, "content"); + if(msg_content_text && json_is_string(msg_content_text)) + msg_content = json_string_value(msg_content_text); + + /* Send PUBLISH */ + nua_publish(nh, + SIPTAG_TO_STR(to), + SIPTAG_EVENT_STR(event_type), + SIPTAG_CALL_ID_STR(callid), + TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)), + SIPTAG_EXPIRES_STR(ttl_text), + TAG_IF(proxy != NULL, NUTAG_PROXY(proxy)), + TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)), + TAG_IF(content_type != NULL && msg_content != NULL, SIPTAG_CONTENT_TYPE_STR(content_type)), + TAG_IF(content_type != NULL && msg_content != NULL, SIPTAG_PAYLOAD_STR(msg_content)), + TAG_IF(etag != NULL, SIPTAG_IF_MATCH_STR(etag)), + TAG_END()); + result = json_object(); + json_object_set_new(result, "event", json_string("publishing")); + if(callid) + json_object_set_new(result, "call_id", json_string(callid)); + } else if(!strcasecmp(request_text, "unpublish")) { + /* Unpublish from some SIP events */ + JANUS_VALIDATE_JSON_OBJECT(root, publish_parameters, + error_code, error_cause, TRUE, + JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto error; + if(session->account.registration_status != janus_sip_registration_status_registered && + session->account.registration_status != janus_sip_registration_status_disabled) { + JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n"); + error_code = JANUS_SIP_ERROR_WRONG_STATE; + g_snprintf(error_cause, 512, "Wrong state (not registered)"); + goto error; + } + const char *to = json_string_value(json_object_get(root, "to")); + if(to == NULL) + to = session->account.identity; + const char *event_type = json_string_value(json_object_get(root, "event")); + + /* Take call-id from request, if it exists */ + const char *callid = NULL; + json_t *request_callid = json_object_get(root, "call_id"); + if(request_callid) + callid = json_string_value(request_callid); + + /* Create a hash key for the publishers table */ + const char *hashkey = event_type; + if(callid) + hashkey = callid; + + /* Get the handle we used for this publishing */ + janus_mutex_lock(&session->stack->smutex); + nua_handle_t *nh = NULL; + if(session->stack->publishers != NULL) + nh = g_hash_table_lookup(session->stack->publishers, (char *)hashkey); + janus_mutex_unlock(&session->stack->smutex); + if(nh == NULL) { + JANUS_LOG(LOG_ERR, "Wrong state (no publishers to this call id or event type)\n"); + error_code = JANUS_SIP_ERROR_WRONG_STATE; + g_snprintf(error_cause, 512, "Wrong state (no publishers to this call id or event type)"); + goto error; + } + + /* Send the PUBLISH with Expires set to 0 */ + nua_publish(nh, + SIPTAG_TO_STR(to), + SIPTAG_EVENT_STR(event_type), + SIPTAG_EXPIRES_STR("0"), TAG_END() + ); + result = json_object(); + json_object_set_new(result, "event", json_string("unpublishing")); + if(callid) + json_object_set_new(result, "call_id", json_string(callid)); } else if(!strcasecmp(request_text, "call")) { /* Call another peer */ if(session->stack == NULL) { @@ -5992,6 +6167,95 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, g_free(messageid); } break; + case nua_r_publish: { + JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); + if(status == 200 || status == 202) { + /* Success */ + json_t *eventj = json_object(); + json_object_set_new(eventj, "sip", json_string("event")); + if(sip && sip->sip_call_id) + json_object_set_new(eventj, "call_id", json_string(sip->sip_call_id->i_id)); + json_t *resultj = json_object(); + json_object_set_new(resultj, "event", json_string("publish_succeeded")); + json_object_set_new(resultj, "code", json_integer(status)); + if(session->incoming_header_prefixes) { + json_t *headers = janus_sip_get_incoming_headers(sip, session); + json_object_set_new(resultj, "headers", headers); + } + json_object_set_new(resultj, "etag", json_string(sip->sip_etag->g_string)); + if (sip && sip->sip_expires) + json_object_set_new(resultj, "expires", json_integer(sip->sip_expires->ex_delta)); + json_object_set_new(resultj, "reason", json_string(phrase ? phrase : "")); + json_object_set_new(eventj, "result", resultj); + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, eventj, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(eventj); + } else if(status == 401 || status == 407) { + const char *scheme = NULL; + const char *realm = NULL; + if(status == 401) { + sip_www_authenticate_t const* www_auth = sip->sip_www_authenticate; + if(www_auth == NULL) + break; + scheme = www_auth->au_scheme; + realm = msg_params_find(www_auth->au_params, "realm="); + } else { + sip_proxy_authenticate_t const* proxy_auth = sip->sip_proxy_authenticate; + if(proxy_auth == NULL) + break; + scheme = proxy_auth->au_scheme; + realm = msg_params_find(proxy_auth->au_params, "realm="); + } + char authuser[100], secret[100]; + memset(authuser, 0, sizeof(authuser)); + memset(secret, 0, sizeof(secret)); + if(session->helper) { + if(session->master == NULL) { + JANUS_LOG(LOG_WARN, "No master session for this helper, authentication will fail...\n"); + } else { + session = session->master; + } + } + if(session->account.authuser && strchr(session->account.authuser, ':')) + g_snprintf(authuser, sizeof(authuser), "\"%s\"", session->account.authuser); + else + g_snprintf(authuser, sizeof(authuser), "%s", session->account.authuser); + if(session->account.secret && strchr(session->account.secret, ':')) + g_snprintf(secret, sizeof(secret), "\"%s\"", session->account.secret); + else + g_snprintf(secret, sizeof(secret), "%s", session->account.secret); + char auth[256]; + memset(auth, 0, sizeof(auth)); + g_snprintf(auth, sizeof(auth), "%s%s:%s:%s:%s%s", + session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "", + scheme, + realm, + authuser, + session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "", + secret); + JANUS_LOG(LOG_VERB, "\t%s\n", auth); + nua_authenticate(nh, NUTAG_AUTH(auth), TAG_END()); + } else if(status >= 400) { + JANUS_LOG(LOG_WARN, "[%s] PUBLISH failed: %d %s\n", session->account.username, status, phrase ? phrase : ""); + json_t *eventj = json_object(); + json_object_set_new(eventj, "sip", json_string("event")); + if(sip && sip->sip_call_id) + json_object_set_new(eventj, "call_id", json_string(sip->sip_call_id->i_id)); + json_t *resultj = json_object(); + json_object_set_new(resultj, "event", json_string("publish_failed")); + json_object_set_new(resultj, "code", json_integer(status)); + json_object_set_new(resultj, "reason", json_string(phrase ? phrase : "")); + if(session->incoming_header_prefixes) { + json_t *headers = janus_sip_get_incoming_headers(sip, session); + json_object_set_new(resultj, "headers", headers); + } + json_object_set_new(eventj, "result", resultj); + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, eventj, NULL); + JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); + json_decref(eventj); + } + break; + } case nua_r_refer: { JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); /* We got a response to our REFER */ @@ -7618,6 +7882,7 @@ gpointer janus_sip_sofia_thread(gpointer user_data) { session->stack->s_nh_m = NULL; session->stack->s_root = su_root_create(session->stack); session->stack->subscriptions = NULL; + session->stack->publishers = NULL; janus_mutex_init(&session->stack->smutex); JANUS_LOG(LOG_VERB, "Setting up sofia stack (sip:%s@%s)\n", session->account.username, local_ip); char sip_url[128]; @@ -7637,7 +7902,7 @@ gpointer janus_sip_sofia_thread(gpointer user_data) { session->stack->s_nua = nua_create(session->stack->s_root, janus_sip_sofia_callback, session, - SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, REFER, MESSAGE, INFO, NOTIFY"), + SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, REFER, MESSAGE, INFO, NOTIFY, PUBLISH"), NUTAG_M_USERNAME(session->account.username), NUTAG_URL(sip_url), TAG_IF(session->account.sips, NUTAG_SIPS_URL(sips_url)), @@ -7675,6 +7940,9 @@ gpointer janus_sip_sofia_thread(gpointer user_data) { if(session->stack->subscriptions != NULL) g_hash_table_unref(session->stack->subscriptions); session->stack->subscriptions = NULL; + if(session->stack->publishers != NULL) + g_hash_table_unref(session->stack->publishers); + session->stack->publishers = NULL; janus_mutex_unlock(&session->stack->smutex); nua_destroy(s_nua); su_root_destroy(session->stack->s_root); From 8749587ef3e0250c725a0a8e51fe7aa1eabe73a5 Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 09:54:28 +0200 Subject: [PATCH 2/9] fix: remove ttl double validation --- src/plugins/janus_sip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index c5b72c1b5d..3d71a3a345 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -3650,7 +3650,7 @@ static void *janus_sip_handler(void *data) { /* TTL */ int ttl = publish_ttl; json_t *pub_ttl = json_object_get(root, "publish_ttl"); - if(pub_ttl && json_is_integer(pub_ttl)) + if(pub_ttl) ttl = json_integer_value(pub_ttl); if(ttl <= 0) ttl = JANUS_DEFAULT_PUBLISH_TTL; From ce6414a6f0d46d540d360a7893b2fb1331889248 Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 10:06:51 +0200 Subject: [PATCH 3/9] fix: publish call_id generating memory leak --- src/plugins/janus_sip.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index 3d71a3a345..bd14d22185 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -3674,8 +3674,9 @@ static void *janus_sip_handler(void *data) { /* If call-id does not exist in request, create a random one */ if(callid == NULL) { JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n"); - callid = g_malloc0(24); - janus_sip_random_string(24, callid); + char new_callid[24]; + janus_sip_random_string(24, new_callid); + callid = new_callid; } /* Prepare or reuse per-event handle (publishers) */ From 76b69afd47b8e6bb3b9a66b5023aab738b2332fa Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 10:13:19 +0200 Subject: [PATCH 4/9] fix: intent --- src/plugins/janus_sip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index bd14d22185..a5e1c3b19a 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -3712,7 +3712,7 @@ static void *janus_sip_handler(void *data) { janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers)); char *contact_header = janus_sip_session_contact_header_retrieve(session); char *proxy = session->helper && session->master ? - session->master->account.outbound_proxy : session->account.outbound_proxy; + session->master->account.outbound_proxy : session->account.outbound_proxy; const char *content_type = NULL; json_t *content_type_text = json_object_get(root, "content_type"); if(content_type_text && json_is_string(content_type_text)) From 0527e966bc2947d1b473438eeb1958c748ff7894 Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 10:17:03 +0200 Subject: [PATCH 5/9] fix: add sip etag responce check --- src/plugins/janus_sip.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index a5e1c3b19a..705521aed8 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -6183,7 +6183,8 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_t *headers = janus_sip_get_incoming_headers(sip, session); json_object_set_new(resultj, "headers", headers); } - json_object_set_new(resultj, "etag", json_string(sip->sip_etag->g_string)); + if(sip && sip->sip_etag && sip->sip_etag->g_string) + json_object_set_new(resultj, "etag", json_string(sip->sip_etag->g_string)); if (sip && sip->sip_expires) json_object_set_new(resultj, "expires", json_integer(sip->sip_expires->ex_delta)); json_object_set_new(resultj, "reason", json_string(phrase ? phrase : "")); From a427c3ce261dbeb5028a099687dfb2795657b7af Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 10:39:40 +0200 Subject: [PATCH 6/9] fix: publish response var names, add missed break --- src/plugins/janus_sip.c | 55 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index 705521aed8..04e923cd01 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -6172,26 +6172,26 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); if(status == 200 || status == 202) { /* Success */ - json_t *eventj = json_object(); - json_object_set_new(eventj, "sip", json_string("event")); + json_t *event = json_object(); + json_object_set_new(event, "sip", json_string("event")); if(sip && sip->sip_call_id) - json_object_set_new(eventj, "call_id", json_string(sip->sip_call_id->i_id)); - json_t *resultj = json_object(); - json_object_set_new(resultj, "event", json_string("publish_succeeded")); - json_object_set_new(resultj, "code", json_integer(status)); + json_object_set_new(event, "call_id", json_string(sip->sip_call_id->i_id)); + json_t *result = json_object(); + json_object_set_new(result, "event", json_string("publish_succeeded")); + json_object_set_new(result, "code", json_integer(status)); if(session->incoming_header_prefixes) { json_t *headers = janus_sip_get_incoming_headers(sip, session); - json_object_set_new(resultj, "headers", headers); + json_object_set_new(result, "headers", headers); } if(sip && sip->sip_etag && sip->sip_etag->g_string) - json_object_set_new(resultj, "etag", json_string(sip->sip_etag->g_string)); + json_object_set_new(result, "etag", json_string(sip->sip_etag->g_string)); if (sip && sip->sip_expires) - json_object_set_new(resultj, "expires", json_integer(sip->sip_expires->ex_delta)); - json_object_set_new(resultj, "reason", json_string(phrase ? phrase : "")); - json_object_set_new(eventj, "result", resultj); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, eventj, NULL); + json_object_set_new(result, "expires", json_integer(sip->sip_expires->ex_delta)); + json_object_set_new(result, "reason", json_string(phrase ? phrase : "")); + json_object_set_new(event, "result", result); + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(eventj); + json_decref(event); } else if(status == 401 || status == 407) { const char *scheme = NULL; const char *realm = NULL; @@ -6236,25 +6236,30 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "", secret); JANUS_LOG(LOG_VERB, "\t%s\n", auth); - nua_authenticate(nh, NUTAG_AUTH(auth), TAG_END()); + /* Authenticate */ + nua_authenticate(nh, + NUTAG_AUTH(auth), + TAG_END()); + break; } else if(status >= 400) { + /* Something went wrong */ JANUS_LOG(LOG_WARN, "[%s] PUBLISH failed: %d %s\n", session->account.username, status, phrase ? phrase : ""); - json_t *eventj = json_object(); - json_object_set_new(eventj, "sip", json_string("event")); + json_t *event = json_object(); + json_object_set_new(event, "sip", json_string("event")); if(sip && sip->sip_call_id) - json_object_set_new(eventj, "call_id", json_string(sip->sip_call_id->i_id)); - json_t *resultj = json_object(); - json_object_set_new(resultj, "event", json_string("publish_failed")); - json_object_set_new(resultj, "code", json_integer(status)); - json_object_set_new(resultj, "reason", json_string(phrase ? phrase : "")); + json_object_set_new(event, "call_id", json_string(sip->sip_call_id->i_id)); + json_t *result = json_object(); + json_object_set_new(result, "event", json_string("publish_failed")); + json_object_set_new(result, "code", json_integer(status)); + json_object_set_new(result, "reason", json_string(phrase ? phrase : "")); if(session->incoming_header_prefixes) { json_t *headers = janus_sip_get_incoming_headers(sip, session); - json_object_set_new(resultj, "headers", headers); + json_object_set_new(result, "headers", headers); } - json_object_set_new(eventj, "result", resultj); - int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, eventj, NULL); + json_object_set_new(event, "result", result); + int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); - json_decref(eventj); + json_decref(event); } break; } From 7cfaa15942e80e3861d0fc032b4c8b125ef376bc Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 10:52:05 +0200 Subject: [PATCH 7/9] feat: publish doc --- src/plugins/janus_sip.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index 04e923cd01..97ce725e8a 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -506,7 +506,7 @@ } \endverbatim * - * As anticipated, SIP events are supported as well, using the SUBSCRIBE + * As anticipated, SIP events are supported as well, using the SUBSCRIBE, PUBLISH * and NOTIFY mechanism. To do that, you need to use the \c subscribe * request, which has to be formatted like this: * @@ -541,6 +541,27 @@ } \endverbatim * + * For publishing, instead, you use the \c publish request, which has to be + * formatted like this: + * +\verbatim +{ + "request" : "publish", + "call_id" : "", + "to" : "", + "event" : "", + "content-type" : "", + "content" : "", + "publish_ttl" : "", + "etag" : "", + "headers" : "" +} +\endverbatim + * A \c publishing event will be sent back, followed by a + * A \c publish_succeeded if the PUBLISH request was accepted, and a + * \c publish_failed if the transaction failed instead. + * + * * You can also record a SIP call, and it works pretty much the same the * VideoCall plugin does. Specifically, you make use of the \c recording * request to either start or stop a recording, using the following syntax: From ed835cdea70d1f8d2175d6ad86ed70223a3df45a Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Thu, 13 Nov 2025 11:15:33 +0200 Subject: [PATCH 8/9] refactor: change publishers storing logic to one per event_type --- src/plugins/janus_sip.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index 97ce725e8a..8d518dd6d7 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -3687,11 +3687,6 @@ static void *janus_sip_handler(void *data) { if(request_callid) callid = json_string_value(request_callid); - /* Create a hash key for the publishers table */ - const char *hashkey = event_type; - if(callid) - hashkey = callid; - /* If call-id does not exist in request, create a random one */ if(callid == NULL) { JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n"); @@ -3704,7 +3699,7 @@ static void *janus_sip_handler(void *data) { nua_handle_t *nh = NULL; janus_mutex_lock(&session->stack->smutex); if(session->stack->publishers != NULL) - nh = g_hash_table_lookup(session->stack->publishers, (char *)hashkey); + nh = g_hash_table_lookup(session->stack->publishers, (char *)event_type); if(nh == NULL) { /* Create a handle in the appropriate NUA */ nua_t *use_nua = NULL; @@ -3722,10 +3717,10 @@ static void *janus_sip_handler(void *data) { nh = nua_handle(use_nua, session, TAG_END()); if(session->stack->publishers == NULL) { /* Create table for mapping publishers too */ - session->stack->publishers = g_hash_table_new_full(g_str_hash, g_str_equal, + session->stack->publishers = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, (GDestroyNotify)nua_handle_destroy); } - g_hash_table_insert(session->stack->publishers, g_strdup(hashkey), nh); + g_hash_table_insert(session->stack->publishers, g_strdup(event_type), nh); } janus_mutex_unlock(&session->stack->smutex); @@ -3779,22 +3774,11 @@ static void *janus_sip_handler(void *data) { to = session->account.identity; const char *event_type = json_string_value(json_object_get(root, "event")); - /* Take call-id from request, if it exists */ - const char *callid = NULL; - json_t *request_callid = json_object_get(root, "call_id"); - if(request_callid) - callid = json_string_value(request_callid); - - /* Create a hash key for the publishers table */ - const char *hashkey = event_type; - if(callid) - hashkey = callid; - /* Get the handle we used for this publishing */ janus_mutex_lock(&session->stack->smutex); nua_handle_t *nh = NULL; if(session->stack->publishers != NULL) - nh = g_hash_table_lookup(session->stack->publishers, (char *)hashkey); + nh = g_hash_table_lookup(session->stack->publishers, (char *)event_type); janus_mutex_unlock(&session->stack->smutex); if(nh == NULL) { JANUS_LOG(LOG_ERR, "Wrong state (no publishers to this call id or event type)\n"); @@ -3811,8 +3795,6 @@ static void *janus_sip_handler(void *data) { ); result = json_object(); json_object_set_new(result, "event", json_string("unpublishing")); - if(callid) - json_object_set_new(result, "call_id", json_string(callid)); } else if(!strcasecmp(request_text, "call")) { /* Call another peer */ if(session->stack == NULL) { From c9eaa71790096ee2e45a9d01bf11c0721d5032bb Mon Sep 17 00:00:00 2001 From: Vladislav Komelkov Date: Mon, 17 Nov 2025 15:12:19 +0200 Subject: [PATCH 9/9] fix: publish call id create bug --- src/plugins/janus_sip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/janus_sip.c b/src/plugins/janus_sip.c index 8d518dd6d7..f7caa3ed55 100644 --- a/src/plugins/janus_sip.c +++ b/src/plugins/janus_sip.c @@ -3688,9 +3688,9 @@ static void *janus_sip_handler(void *data) { callid = json_string_value(request_callid); /* If call-id does not exist in request, create a random one */ + char new_callid[24]; if(callid == NULL) { JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n"); - char new_callid[24]; janus_sip_random_string(24, new_callid); callid = new_callid; }