summaryrefslogtreecommitdiff
path: root/src/rexmpp_jingle.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rexmpp_jingle.c')
-rw-r--r--src/rexmpp_jingle.c2472
1 files changed, 2472 insertions, 0 deletions
diff --git a/src/rexmpp_jingle.c b/src/rexmpp_jingle.c
new file mode 100644
index 0000000..2b84445
--- /dev/null
+++ b/src/rexmpp_jingle.c
@@ -0,0 +1,2472 @@
+/**
+ @file rexmpp_jingle.c
+ @brief Jingle routines
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+The following XEPs are handled here so far:
+
+- XEP-0166: Jingle
+
+File transfer over IBB:
+
+- XEP-0234: Jingle File Transfer
+- XEP-0261: Jingle In-Band Bytestreams Transport Method
+
+A/V calls over ICE-UDP + DTLS-SRTP:
+
+- XEP-0167: Jingle RTP Sessions
+- XEP-0176: Jingle ICE-UDP Transport Method
+- XEP-0320: Use of DTLS-SRTP in Jingle Sessions
+- XEP-0215: External Service Discovery
+
+*/
+
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include "config.h"
+
+#ifdef ENABLE_CALLS
+#include <inttypes.h>
+#include <glib.h>
+#include <gio/gnetworking.h>
+#include <nice.h>
+#include <agent.h>
+#include <srtp2/srtp.h>
+#include <math.h>
+#include "portaudio.h"
+#ifdef HAVE_OPUS
+#include <opus.h>
+#endif
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_xml.h"
+#include "rexmpp_jingle.h"
+#include "rexmpp_base64.h"
+#include "rexmpp_random.h"
+#include "rexmpp_tls.h"
+#include "rexmpp_digest.h"
+
+
+/* https://en.wikipedia.org/wiki/G.711 */
+uint8_t rexmpp_pcma_encode (int16_t x) {
+ uint8_t sign = 0x80;
+ uint8_t pos;
+ uint8_t val = 0;
+ if (x < 0) {
+ x = -x;
+ sign = 0;
+ }
+ x >>= 3;
+ for (pos = 11; pos > 5 && ! (x & (1 << pos)); pos--);
+ val = (x >> (pos - 4)) & 0xf;
+ return (sign | ((pos - 4) << 4) | val) ^ 0x55;
+}
+
+int16_t rexmpp_pcma_decode (uint8_t x) {
+ x ^= 0x55;
+ int16_t sign = -1;
+ uint8_t shift = (x >> 4) & 7;
+ int16_t val = x & 0xf;
+ if (x & 0x80) {
+ x ^= 0x80;
+ sign = 1;
+ }
+ val = (val << 1) | 1;
+ if (shift > 0) {
+ val = (val | 0x20) << (shift - 1);
+ }
+ val <<= 3;
+ return sign * val;
+}
+
+uint8_t rexmpp_pcmu_encode (int16_t x) {
+ uint8_t sign = 0;
+ uint8_t pos;
+ uint8_t val = 0;
+ if (x < 0) {
+ x = -x;
+ sign = 0x80;
+ }
+ x >>= 2;
+ for (pos = 12; pos > 5 && ! (x & (1 << pos)); pos--);
+ val = (x >> (pos - 4)) & 0xf;
+ return (sign | ((pos - 4) << 4) | val) ^ 0xff;
+}
+
+int16_t rexmpp_pcmu_decode (uint8_t x) {
+ x ^= 0xff;
+ int16_t sign = 1;
+ uint8_t shift = (x >> 4) & 7;
+ int16_t val = x & 0xf;
+ if (x & 0x80) {
+ x ^= 0x80;
+ sign = -1;
+ }
+ val = (val << 1) | 1;
+ if (shift > 0) {
+ val = (val | 0x20) << (shift - 1);
+ }
+ val <<= 2;
+ return sign * val;
+}
+
+rexmpp_xml_t *
+rexmpp_jingle_session_payload_by_id (rexmpp_jingle_session_t *sess,
+ int payload_type_id)
+{
+ if (sess->accept == NULL) {
+ return NULL;
+ }
+ rexmpp_xml_t *descr_child =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (sess->accept, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (descr_child != NULL) {
+ if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1",
+ "payload-type"))
+ {
+ const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id");
+ if (pl_id != NULL && atoi(pl_id) == payload_type_id) {
+ return descr_child;
+ }
+ }
+ descr_child = rexmpp_xml_next_elem_sibling(descr_child);
+ }
+ return NULL;
+}
+
+#ifdef ENABLE_CALLS
+int rexmpp_jingle_session_configure_audio (rexmpp_jingle_session_t *sess) {
+ if (sess->accept == NULL) {
+ return 0;
+ }
+ rexmpp_xml_t *descr_child =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child(sess->accept, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (descr_child != NULL) {
+ if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1",
+ "payload-type"))
+ {
+ const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id");
+ if (pl_id != NULL) {
+ if (atoi(pl_id) == 0) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to PCMU (0) for Jingle session %s",
+ sess->sid);
+ sess->codec = REXMPP_CODEC_PCMU;
+ sess->payload_type = 0;
+ return sess->payload_type;
+ }
+ if (atoi(pl_id) == 8) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to PCMA (8) for Jingle session %s",
+ sess->sid);
+ sess->codec = REXMPP_CODEC_PCMA;
+ sess->payload_type = 8;
+ return sess->payload_type;
+ }
+#ifdef HAVE_OPUS
+ const char *pl_name = rexmpp_xml_find_attr_val(descr_child, "name");
+ if ((pl_name != NULL) && (strcmp(pl_name, "opus") == 0)) {
+ sess->codec = REXMPP_CODEC_OPUS;
+ sess->payload_type = atoi(pl_id);
+ rexmpp_log(sess->s, LOG_INFO,
+ "Setting the codec to Opus (%u) for Jingle session %s",
+ sess->payload_type, sess->sid);
+ return sess->payload_type;
+ }
+#endif /* HAVE_OPUS */
+ }
+ }
+ descr_child = rexmpp_xml_next_elem_sibling(descr_child);
+ }
+ return 0;
+}
+
+
+static int rexmpp_pa_callback (const void *input,
+ void *output,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData)
+{
+ (void)timeInfo;
+ (void)statusFlags;
+ struct pa_buffers *data = (struct pa_buffers*)userData;
+ int16_t *out = (int16_t*)output;
+ int16_t *in = (int16_t*)input;
+ unsigned int i;
+ for (i = 0; i < frameCount; i++) {
+ if (in != NULL) {
+ data->capture.buf[data->capture.write_pos] = in[i];
+ data->capture.write_pos++;
+ data->capture.write_pos %= PA_BUF_SIZE;
+ }
+
+ if (data->playback.read_pos != data->playback.write_pos) {
+ out[i] = data->playback.buf[data->playback.read_pos];
+ data->playback.read_pos++;
+ data->playback.read_pos %= PA_BUF_SIZE;
+ } else {
+ out[i] = 0;
+ }
+ }
+ return 0;
+}
+
+void
+rexmpp_jingle_run_audio (rexmpp_jingle_session_t *sess) {
+ rexmpp_random_buf(&(sess->rtp_seq_num), sizeof(uint16_t));
+ sess->rtp_last_seq_num = 0xffff;
+ rexmpp_random_buf(&(sess->rtp_timestamp), sizeof(uint32_t));
+ rexmpp_random_buf(&(sess->rtp_ssrc), sizeof(uint32_t));
+
+ int rate = 8000;
+ int channels = 1;
+#ifdef HAVE_OPUS
+ if (sess->codec == REXMPP_CODEC_OPUS) {
+ rate = 48000;
+ channels = 2;
+ int opus_error;
+ sess->opus_enc =
+ opus_encoder_create(rate, channels, OPUS_APPLICATION_VOIP, &opus_error);
+ sess->opus_dec =
+ opus_decoder_create(rate, channels, &opus_error);
+ }
+#endif /* HAVE_OPUS */
+
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "Setting up an audio stream: SSRC %" PRIx32
+ ", %d Hz, %d channels",
+ sess->rtp_ssrc, rate, channels);
+
+ PaError err = Pa_OpenDefaultStream (&(sess->pa_stream),
+ channels,
+ channels,
+ paInt16,
+ /* paFloat32, */
+ rate,
+ /* 480, */
+ paFramesPerBufferUnspecified,
+ rexmpp_pa_callback,
+ &(sess->ring_buffers));
+ if (err != paNoError) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to open a PA stream: %s",
+ Pa_GetErrorText(err));
+ }
+ err = Pa_StartStream(sess->pa_stream);
+ if (err != paNoError) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to start a PA stream: %s",
+ Pa_GetErrorText(err));
+ }
+}
+#endif /* ENABLE_CALLS */
+
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_by_id (rexmpp_t *s, const char *sid) {
+ if (sid == NULL) {
+ return NULL;
+ }
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ if (strcmp(cur->sid, sid) == 0) {
+ return cur;
+ }
+ cur = cur->next;
+ }
+ rexmpp_log(s, LOG_WARNING, "No Jingle session with sid %s found", sid);
+ return NULL;
+}
+
+void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) {
+ if (session->jid != NULL) {
+ free(session->jid);
+ }
+ if (session->sid != NULL) {
+ free(session->sid);
+ }
+ if (session->initiate != NULL) {
+ rexmpp_xml_free_list(session->initiate);
+ }
+ if (session->accept != NULL) {
+ rexmpp_xml_free_list(session->accept);
+ }
+ if (session->ibb_fh != NULL) {
+ fclose(session->ibb_fh);
+ }
+#ifdef ENABLE_CALLS
+ if (session->type == REXMPP_JINGLE_SESSION_MEDIA) {
+ int i;
+ if (session->pa_stream != NULL) {
+ PaError err = Pa_StopStream(session->pa_stream);
+ if (err != paNoError) {
+ rexmpp_log(session->s, LOG_ERR,
+ "Failed to close a PortAudio stream: %s",
+ Pa_GetErrorText(err));
+ }
+ session->pa_stream = NULL;
+ }
+#ifdef HAVE_OPUS
+ if (session->opus_enc != NULL) {
+ opus_encoder_destroy(session->opus_enc);
+ session->opus_enc = NULL;
+ }
+ if (session->opus_dec != NULL) {
+ opus_decoder_destroy(session->opus_dec);
+ session->opus_dec = NULL;
+ }
+#endif /* HAVE_OPUS */
+ for (i = 0; i < 2; i++) {
+ rexmpp_jingle_component_t *comp = &session->component[i];
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE ||
+ comp->dtls_state == REXMPP_TLS_CLOSING ||
+ comp->dtls_state == REXMPP_TLS_CLOSED) {
+ /* SRTP structures are allocated upon a TLS connection, so
+ using the TLS state to find when they should be
+ deallocated. */
+ if (comp->srtp_in != NULL) {
+ srtp_dealloc(comp->srtp_in);
+ }
+ if (comp->srtp_out != NULL) {
+ srtp_dealloc(comp->srtp_out);
+ }
+ }
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE ||
+ comp->dtls_state == REXMPP_TLS_ACTIVE ||
+ comp->dtls_state == REXMPP_TLS_CLOSING ||
+ comp->dtls_state == REXMPP_TLS_CLOSED) {
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE ||
+ comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ rexmpp_tls_disconnect(comp->s, comp->dtls);
+ }
+ rexmpp_tls_session_free(comp->dtls);
+ rexmpp_tls_ctx_free(comp->dtls);
+ comp->dtls = NULL;
+ comp->dtls_state = REXMPP_TLS_INACTIVE;
+ }
+ }
+ if (session->ice_agent != NULL) {
+ nice_agent_close_async(session->ice_agent, NULL, NULL);
+ g_object_unref(session->ice_agent);
+ session->ice_agent = NULL;
+ }
+ if (session->stun_host != NULL) {
+ free(session->stun_host);
+ session->stun_host = NULL;
+ }
+ if (session->turn_host != NULL) {
+ free(session->turn_host);
+ session->turn_host = NULL;
+ }
+ if (session->turn_username != NULL) {
+ free(session->turn_username);
+ session->turn_username = NULL;
+ }
+ if (session->turn_password != NULL) {
+ free(session->turn_password);
+ session->turn_password = NULL;
+ }
+ }
+#endif /* ENABLE_CALLS */
+ free(session);
+}
+
+void rexmpp_jingle_session_delete (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ if (sess == NULL) {
+ return;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Removing Jingle session %s", sess->sid);
+ rexmpp_jingle_session_t *cur = s->jingle->sessions, *prev = NULL;
+ while (cur != NULL) {
+ if (sess == cur) {
+ if (prev == NULL) {
+ s->jingle->sessions = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ rexmpp_jingle_session_destroy(sess);
+ return;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+}
+
+void rexmpp_jingle_session_delete_by_id (rexmpp_t *s, const char *sid) {
+ rexmpp_jingle_session_delete(s, rexmpp_jingle_session_by_id(s, sid));
+}
+
+int rexmpp_jingle_session_add (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ uint32_t sessions_num = 0;
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ sessions_num++;
+ cur = cur->next;
+ }
+ if (sessions_num >= s->max_jingle_sessions) {
+ rexmpp_log(s, LOG_ERR, "Too many Jingle sessions, discaring a new one");
+ rexmpp_jingle_session_destroy(sess);
+ return 0;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Adding Jingle session %s", sess->sid);
+ sess->next = s->jingle->sessions;
+ s->jingle->sessions = sess;
+ return 1;
+}
+
+int rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess);
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_create (rexmpp_t *s,
+ char *jid,
+ char *sid,
+ enum rexmpp_jingle_session_type type,
+ int initiator)
+{
+ rexmpp_jingle_session_t *sess = malloc(sizeof(rexmpp_jingle_session_t));
+ if (sess != NULL) {
+ sess->jid = jid;
+ sess->sid = sid;
+ sess->initiate = NULL;
+ sess->accept = NULL;
+ sess->s = s;
+ sess->initiator = initiator;
+ sess->type = type;
+ sess->ibb_fh = NULL;
+ sess->ibb_sid = NULL;
+ sess->ibb_seq = 0;
+#ifdef ENABLE_CALLS
+ sess->stun_host = NULL;
+ sess->stun_port = 0;
+ sess->turn_host = NULL;
+ sess->turn_port = 0;
+ sess->turn_username = NULL;
+ sess->turn_password = NULL;
+
+ int i;
+ for (i = 0; i < 2; i++) {
+ sess->component[i].component_id = i + 1;
+ sess->component[i].session = sess;
+ sess->component[i].s = s;
+ sess->component[i].dtls_state = REXMPP_TLS_INACTIVE;
+ sess->component[i].dtls = rexmpp_tls_ctx_new(s, 1);
+ sess->component[i].srtp_out = NULL;
+ sess->component[i].srtp_in = NULL;
+ }
+
+ sess->rtcp_mux = s->jingle_prefer_rtcp_mux;
+ sess->ice_agent = NULL;
+
+ sess->pa_stream = NULL;
+
+ sess->ring_buffers.capture.read_pos = 0;
+ sess->ring_buffers.capture.write_pos = 0;
+ sess->ring_buffers.playback.read_pos = 0;
+ sess->ring_buffers.playback.write_pos = 0;
+ sess->codec = REXMPP_CODEC_UNDEFINED;
+ sess->payload_type = 0;
+
+#ifdef HAVE_OPUS
+ sess->opus_enc = NULL;
+ sess->opus_dec = NULL;
+#endif /* HAVE_POUS */
+#endif /* ENABLE_CALLS */
+ if (! rexmpp_jingle_session_add(s, sess)) {
+ /* The session is destroyed by rexmpp_jingle_session_add */
+ return NULL;
+ }
+ return sess;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a Jingle session");
+ return NULL;
+ }
+}
+
+rexmpp_jingle_session_t *
+rexmpp_jingle_session_by_ibb_sid (rexmpp_t *s, const char *ibb_sid) {
+ if (ibb_sid == NULL) {
+ return NULL;
+ }
+ rexmpp_jingle_session_t *cur = s->jingle->sessions;
+ while (cur != NULL) {
+ if (cur->type == REXMPP_JINGLE_SESSION_FILE &&
+ strcmp(cur->ibb_sid, ibb_sid) == 0) {
+ return cur;
+ }
+ cur = cur->next;
+ }
+ rexmpp_log(s, LOG_WARNING,
+ "No Jingle session with ibb_sid %s found", ibb_sid);
+ return NULL;
+}
+
+int rexmpp_jingle_init (rexmpp_t *s) {
+ s->jingle = malloc(sizeof(struct rexmpp_jingle_ctx));
+ s->jingle->sessions = NULL;
+#ifdef ENABLE_CALLS
+ g_networking_init();
+ srtp_init();
+ s->jingle->gloop = g_main_loop_new(NULL, FALSE);
+ Pa_Initialize();
+ nice_debug_enable(1);
+#endif /* ENABLE_CALLS */
+ return 0;
+}
+
+void rexmpp_jingle_stop (rexmpp_t *s) {
+ while (s->jingle->sessions != NULL) {
+ rexmpp_jingle_session_delete(s, s->jingle->sessions);
+ }
+#ifdef ENABLE_CALLS
+ g_main_loop_quit(s->jingle->gloop);
+ s->jingle->gloop = NULL;
+ srtp_shutdown();
+ Pa_Terminate();
+#endif /* ENABLE_CALLS */
+ free(s->jingle);
+ s->jingle = NULL;
+}
+
+
+void rexmpp_jingle_accept_file_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to accept a Jingle file transfer");
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_accept_file (rexmpp_t *s,
+ rexmpp_jingle_session_t *session,
+ const char *path)
+{
+ session->ibb_fh = fopen(path, "wb");
+ if (session->ibb_fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for writing: %s",
+ path, strerror(errno));
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_xml_t *jingle = session->initiate;
+ rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+
+ rexmpp_xml_t *new_jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(new_jingle, "action", "session-accept");
+ rexmpp_xml_add_attr(new_jingle, "responder", s->assigned_jid.full);
+ rexmpp_xml_add_attr(new_jingle, "sid", session->sid);
+ rexmpp_xml_add_child(new_jingle, rexmpp_xml_clone(content));
+ session->accept = rexmpp_xml_clone(new_jingle);
+ return rexmpp_iq_new(s, "set", session->jid, new_jingle,
+ rexmpp_jingle_accept_file_cb, strdup(session->sid));
+}
+
+rexmpp_err_t
+rexmpp_jingle_accept_file_by_id (rexmpp_t *s,
+ const char *sid,
+ const char *path)
+{
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ return rexmpp_jingle_accept_file(s, session, path);
+}
+
+void rexmpp_jingle_session_terminate_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to terminate session %s, removing anyway",
+ sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_session_terminate (rexmpp_t *s,
+ const char *sid,
+ rexmpp_xml_t *reason_node,
+ const char *reason_text)
+{
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "action", "session-terminate");
+ rexmpp_xml_add_attr(jingle, "sid", sid);
+ rexmpp_xml_t *reason =
+ rexmpp_xml_new_elem("reason", "urn:xmpp:jingle:1");
+ if (reason_text != NULL) {
+ rexmpp_xml_t *text =
+ rexmpp_xml_new_elem("text", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_text(text, reason_text);
+ rexmpp_xml_add_child(reason, text);
+ }
+ rexmpp_xml_add_child(reason, reason_node);
+ rexmpp_xml_add_child(jingle, reason);
+ rexmpp_err_t ret = rexmpp_iq_new(s, "set", session->jid, jingle,
+ rexmpp_jingle_session_terminate_cb,
+ strdup(sid));
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ return ret;
+}
+
+void rexmpp_jingle_send_file_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to initiate file sending for sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+rexmpp_err_t
+rexmpp_jingle_send_file (rexmpp_t *s,
+ const char *jid,
+ char *path)
+{
+ /* Open the file and calculate its hash before allocating the other
+ things, so we can easily return on failure. */
+ FILE *fh = fopen(path, "rb");
+ if (fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for reading", path);
+ return REXMPP_E_OTHER;
+ }
+
+ char buf[4096];
+ rexmpp_digest_t sha256, sha3_256;
+ if (rexmpp_digest_init(&sha256, REXMPP_DIGEST_SHA256)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-256 digest object");
+ fclose(fh);
+ return REXMPP_E_OTHER;
+ }
+ if (rexmpp_digest_init(&sha3_256, REXMPP_DIGEST_SHA3_256)) {
+ rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-3-256 digest object");
+ rexmpp_digest_finish(&sha256, NULL, 0);
+ fclose(fh);
+ return REXMPP_E_OTHER;
+ }
+ size_t len = fread(buf, 1, 4096, fh);
+ while (len > 0) {
+ rexmpp_digest_update(&sha256, buf, len);
+ rexmpp_digest_update(&sha3_256, buf, len);
+ len = fread(buf, 1, 4096, fh);
+ }
+
+ char *sid = rexmpp_random_id();
+ char *ibb_sid = rexmpp_random_id();
+
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "action", "session-initiate");
+ rexmpp_xml_add_attr(jingle, "sid", sid);
+ rexmpp_xml_add_attr(jingle, "initiator", s->assigned_jid.full);
+
+ rexmpp_xml_t *content =
+ rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(content, "creator", "initiator");
+ rexmpp_xml_add_attr(content, "name", "IBB file");
+ rexmpp_xml_add_child(jingle, content);
+
+ rexmpp_xml_t *transport =
+ rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ibb:1");
+ rexmpp_xml_add_attr(transport, "block-size", "4096");
+ rexmpp_xml_add_attr(transport, "sid", ibb_sid);
+ rexmpp_xml_add_child(content, transport);
+ rexmpp_xml_t *description =
+ rexmpp_xml_new_elem("description", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_child(content, description);
+ rexmpp_xml_t *file =
+ rexmpp_xml_new_elem("file", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_child(description, file);
+ rexmpp_xml_t *file_name =
+ rexmpp_xml_new_elem("name", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_text(file_name, basename(path));
+ rexmpp_xml_add_child(file, file_name);
+
+ char hash[32];
+ char *hash_base64 = NULL;
+ size_t hash_base64_len = 0;
+
+ rexmpp_digest_finish(&sha256, hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA256));
+ rexmpp_base64_to(hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA256),
+ &hash_base64,
+ &hash_base64_len);
+ rexmpp_xml_t *file_hash =
+ rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2");
+ rexmpp_xml_add_attr(file_hash, "algo", "sha-256");
+ rexmpp_xml_add_text(file_hash, hash_base64);
+ free(hash_base64);
+ rexmpp_xml_add_child(file, file_hash);
+
+ hash_base64 = NULL;
+ hash_base64_len = 0;
+ rexmpp_digest_finish(&sha3_256, hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA3_256));
+ rexmpp_base64_to(hash,
+ rexmpp_digest_len(REXMPP_DIGEST_SHA3_256),
+ &hash_base64,
+ &hash_base64_len);
+ file_hash = rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2");
+ rexmpp_xml_add_attr(file_hash, "algo", "sha3-256");
+ rexmpp_xml_add_text(file_hash, hash_base64);
+ free(hash_base64);
+ rexmpp_xml_add_child(file, file_hash);
+
+ long fsize = ftell(fh);
+ fseek(fh, 0, SEEK_SET);
+ snprintf(buf, 11, "%ld", fsize);
+ rexmpp_xml_t *file_size =
+ rexmpp_xml_new_elem("size", "urn:xmpp:jingle:apps:file-transfer:5");
+ rexmpp_xml_add_text(file_size, buf);
+ rexmpp_xml_add_child(file, file_size);
+
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(jid), sid, REXMPP_JINGLE_SESSION_FILE, 1);
+ if (sess != NULL) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ sess->ibb_sid = ibb_sid;
+ sess->ibb_fh = fh;
+ return rexmpp_iq_new(s, "set", sess->jid, jingle,
+ rexmpp_jingle_send_file_cb, strdup(sess->sid));
+ } else {
+ return REXMPP_E_OTHER;
+ }
+}
+
+
+void rexmpp_jingle_ibb_close_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (success) {
+ rexmpp_log(s, LOG_DEBUG, "Closed IBB stream for Jingle stream %s", sid);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to close IBB stream for Jingle stream %s", sid);
+ }
+ free(sid);
+}
+
+void rexmpp_jingle_ibb_send_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "An IBB stream error for Jingle sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ free(sid);
+ return;
+ }
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session == NULL) {
+ rexmpp_log(s, LOG_ERR, "Jingle session %s doesn't exist", sid);
+ free(sid);
+ return;
+ }
+ if (feof(session->ibb_fh)) {
+ rexmpp_xml_t *close =
+ rexmpp_xml_new_elem("close", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(close, "sid", session->ibb_sid);
+ rexmpp_iq_new(s, "set", session->jid, close,
+ rexmpp_jingle_ibb_close_cb, sid);
+ return;
+ } else {
+ char buf[4096];
+ size_t len = fread(buf, 1, 4096, session->ibb_fh);
+ if (len > 0) {
+ rexmpp_xml_t *data =
+ rexmpp_xml_new_elem("data", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(data, "sid", session->ibb_sid);
+ char *out = NULL;
+ size_t out_len = 0;
+ rexmpp_base64_to(buf, len, &out, &out_len);
+ rexmpp_xml_add_text(data, out);
+ free(out);
+ snprintf(buf, 11, "%u", session->ibb_seq);
+ rexmpp_xml_add_attr(data, "seq", buf);
+ session->ibb_seq++;
+ rexmpp_iq_new(s, "set", session->jid, data,
+ rexmpp_jingle_ibb_send_cb, sid);
+ return;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to read from a file: %s ", strerror(errno));
+ rexmpp_jingle_session_terminate(s, sid,
+ rexmpp_xml_new_elem("media-error",
+ "urn:xmpp:jingle:1"),
+ "File reading error");
+ }
+ }
+ free(sid);
+}
+
+#ifdef ENABLE_CALLS
+void rexmpp_jingle_call_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to initiate a call for sid %s", sid);
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ }
+ free(sid);
+}
+
+void
+rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess,
+ rexmpp_xml_t *transport)
+{
+ if (sess->ice_agent == NULL) {
+ /* Must be an incoming call; just add candidates to
+ session-initiate's transport. */
+ rexmpp_xml_t *old_transport =
+ rexmpp_xml_find_child(rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1",
+ "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport);
+ while (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1",
+ "candidate")) {
+ rexmpp_xml_add_child(old_transport, rexmpp_xml_clone(candidate));
+ candidate = rexmpp_xml_next_elem_sibling(candidate);
+ }
+ return;
+ }
+ const char *ufrag = rexmpp_xml_find_attr_val(transport, "ufrag");
+ const char *password = rexmpp_xml_find_attr_val(transport, "pwd");
+ nice_agent_set_remote_credentials(sess->ice_agent, sess->ice_stream_id,
+ ufrag, password);
+
+ int component_id;
+
+ for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) {
+ GSList *remote_candidates =
+ nice_agent_get_remote_candidates(sess->ice_agent,
+ sess->ice_stream_id,
+ component_id);
+ rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport);
+ while (candidate != NULL) {
+ if (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1",
+ "candidate")) {
+ const char *component = rexmpp_xml_find_attr_val(candidate, "component");
+ if (component[0] == component_id + '0') {
+ const char *type_str = rexmpp_xml_find_attr_val(candidate, "type");
+ int type_n = NICE_CANDIDATE_TYPE_HOST;
+ if (strcmp(type_str, "host") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_HOST;
+ } else if (strcmp(type_str, "srflx") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
+ } else if (strcmp(type_str, "prflx") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
+ } else if (strcmp(type_str, "relay") == 0) {
+ type_n = NICE_CANDIDATE_TYPE_RELAYED;
+ }
+ NiceCandidate *c = nice_candidate_new(type_n);
+ c->component_id = component_id;
+ c->stream_id = sess->ice_stream_id;
+
+ const char *foundation = rexmpp_xml_find_attr_val(candidate, "foundation");
+ strncpy(c->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION - 1);
+ c->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
+
+ c->transport = NICE_CANDIDATE_TRANSPORT_UDP;
+
+ const char *priority = rexmpp_xml_find_attr_val(candidate, "priority");
+ c->priority = atoi(priority);
+
+ const char *ip = rexmpp_xml_find_attr_val(candidate, "ip");
+ if (! nice_address_set_from_string(&c->addr, ip)) {
+ rexmpp_log(sess->s, LOG_ERR,
+ "Failed to parse an ICE-UDP candidate's address: %s",
+ ip);
+ }
+
+ const char *port = rexmpp_xml_find_attr_val(candidate, "port");
+ nice_address_set_port(&c->addr, atoi(port));
+
+ remote_candidates = g_slist_prepend(remote_candidates, c);
+ }
+ }
+ candidate = rexmpp_xml_next_elem_sibling(candidate);
+ }
+ if (remote_candidates != NULL) {
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "Setting %d remote candidates for component %d",
+ g_slist_length(remote_candidates),
+ component_id);
+ nice_agent_set_remote_candidates(sess->ice_agent, sess->ice_stream_id,
+ component_id, remote_candidates);
+ g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
+ } else {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "No remote candidates for component %d",
+ component_id);
+ }
+ }
+}
+
+/* Checks whether we are in the active (client) role for DTLS, based
+ on either "session-initiate" or "session-accept" message. */
+int rexmpp_jingle_dtls_is_active (rexmpp_jingle_session_t *sess, int in_initiate) {
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (in_initiate ? sess->initiate : sess->accept,
+ "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1", "transport"),
+ "urn:xmpp:jingle:apps:dtls:0", "fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(sess->s, LOG_ERR, "No fingerprint in the 'session-%s' Jingle element",
+ in_initiate ? "initiate" : "accept");
+ return 0;
+ }
+ const char *fingerprint_setup = rexmpp_xml_find_attr_val(fingerprint, "setup");
+ if (fingerprint_setup == NULL) {
+ rexmpp_log(sess->s, LOG_ERR, "No 'setup' attribute for a fingerprint element");
+ return 0;
+ }
+ int active = 0;
+ if (sess->initiator) {
+ if (in_initiate) {
+ active = (strcmp(fingerprint_setup, "active") == 0);
+ } else {
+ active = (strcmp(fingerprint_setup, "active") != 0);
+ }
+ } else {
+ if (in_initiate) {
+ active = (strcmp(fingerprint_setup, "active") != 0);
+ } else {
+ active = (strcmp(fingerprint_setup, "active") == 0);
+ }
+ }
+ return active;
+}
+
+
+void rexmpp_transport_info_call_cb (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *request,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)request;
+ (void)response;
+ char *sid = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to send additional candidate for Jingle session %s",
+ sid);
+ }
+ free(ptr);
+}
+
+void
+rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent,
+ guint stream_id,
+ gpointer data)
+{
+ rexmpp_jingle_session_t *sess = data;
+
+ rexmpp_log(sess->s, LOG_DEBUG, "ICE agent candidate gathering is done");
+
+ /* We'll need a fingerprint a bit later, but checking it before
+ allocating other things. */
+ char fp[32], fp_str[32 * 3 + 1];
+ size_t fp_size = 32;
+ if (rexmpp_tls_my_fp(sess->s, fp, fp_str, &fp_size)) {
+ return;
+ }
+
+ rexmpp_xml_t *jingle = rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle, "sid", sess->sid);
+
+ rexmpp_xml_t *content = rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(content, "creator", "initiator");
+ rexmpp_xml_add_attr(content, "senders", "both");
+ rexmpp_xml_t *description;
+ if (sess->initiator) {
+ /* We are the intiator: send the predefined options. */
+ rexmpp_xml_add_attr(jingle, "action", "session-initiate");
+ rexmpp_xml_add_attr(jingle, "initiator", sess->s->assigned_jid.full);
+ rexmpp_xml_add_attr(content, "name", "call");
+
+ /* https://datatracker.ietf.org/doc/html/rfc4568 */
+ rexmpp_xml_t *encryption =
+ rexmpp_xml_new_elem("encryption", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(encryption, "required", "true");
+ rexmpp_xml_add_child(content, encryption);
+ rexmpp_xml_t *crypto =
+ rexmpp_xml_new_elem("crypto", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_attr(crypto, "crypto-suite", "AES_CM_128_HMAC_SHA1_80");
+ rexmpp_xml_add_attr(crypto, "tag", "1");
+ rexmpp_xml_add_child(encryption, crypto);
+
+ description = rexmpp_xml_clone(sess->s->jingle_rtp_description);
+ } else {
+ /* Accepting the call, will have to compare payload type options
+ to supported and preferred ones, and pick some from those. */
+ rexmpp_xml_t *init_jingle = sess->initiate;
+ rexmpp_xml_t *init_content =
+ rexmpp_xml_find_child(init_jingle, "urn:xmpp:jingle:1", "content");
+ const char *init_content_name = rexmpp_xml_find_attr_val(init_content, "name");
+ if (init_content_name != NULL) {
+ rexmpp_xml_add_attr(content, "name", init_content_name);
+ } else {
+ rexmpp_log(sess->s, LOG_ERR,
+ "Empty content name for Jingle session %s with %s",
+ sess->sid, sess->jid);
+ }
+ rexmpp_xml_add_attr(jingle, "action", "session-accept");
+ rexmpp_xml_add_attr(jingle, "initiator", sess->jid);
+ rexmpp_xml_add_attr(jingle, "responder", sess->s->assigned_jid.full);
+
+ description = rexmpp_xml_clone(sess->s->jingle_rtp_description);
+ /* Find the first matching payload-type and add that */
+ rexmpp_xml_t *pl_type =
+ rexmpp_xml_first_elem_child(sess->s->jingle_rtp_description);
+ rexmpp_xml_t *selected_pl = NULL;
+ while (pl_type != NULL) {
+ if (rexmpp_xml_match(pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) {
+ const char *pl_id = rexmpp_xml_find_attr_val(pl_type, "id");
+ if (pl_id != NULL) {
+ int pl_id_num = atoi(pl_id);
+ rexmpp_xml_t *proposed_pl_type =
+ rexmpp_xml_first_elem_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:apps:rtp:1", "description"));
+ while (proposed_pl_type != NULL && selected_pl == NULL) {
+ if (rexmpp_xml_match(proposed_pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) {
+ const char *proposed_pl_id = rexmpp_xml_find_attr_val(proposed_pl_type, "id");
+ if (proposed_pl_id != NULL) {
+ int proposed_pl_id_num = atoi(proposed_pl_id);
+ if (pl_id_num < 96 && pl_id_num == proposed_pl_id_num) {
+ selected_pl = pl_type;
+ } else {
+ const char *pl_name =
+ rexmpp_xml_find_attr_val(pl_type, "name");
+ if (pl_name != NULL) {
+ const char *proposed_pl_name =
+ rexmpp_xml_find_attr_val(proposed_pl_type, "name");
+ if (proposed_pl_name != NULL) {
+ if (strcmp(pl_name, proposed_pl_name) == 0) {
+ /* todo: compare clock rates, numbers of
+ channels, parameters */
+ selected_pl = pl_type;
+ }
+ }
+ }
+ }
+ }
+ }
+ proposed_pl_type = rexmpp_xml_next_elem_sibling(proposed_pl_type);
+ }
+ } else {
+ rexmpp_log(sess->s, LOG_ERR,
+ "No 'id' specified for a payload-type element.");
+ }
+ }
+ pl_type = pl_type->next;
+ }
+ if (selected_pl != NULL) {
+ rexmpp_xml_add_child(description, rexmpp_xml_clone(selected_pl));
+ } else {
+ rexmpp_log(sess->s, LOG_ERR, "No suitable payload type found");
+ /* todo: fail if it's NULL, though it shouldn't happen, since
+ PCMU and PCMA are mandatory */
+ }
+ }
+
+ rexmpp_xml_add_child(jingle, content);
+ rexmpp_xml_add_child(content, description);
+
+ if (sess->rtcp_mux) {
+ rexmpp_xml_t *rtcp_mux =
+ rexmpp_xml_new_elem("rtcp-mux", "urn:xmpp:jingle:apps:rtp:1");
+ rexmpp_xml_add_child(description, rtcp_mux);
+ }
+
+ rexmpp_xml_t *transport =
+ rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ice-udp:1");
+ gchar *ufrag = NULL;
+ gchar *password = NULL;
+ nice_agent_get_local_credentials(agent, stream_id, &ufrag, &password);
+ rexmpp_xml_add_attr(transport, "ufrag", ufrag);
+ rexmpp_xml_add_attr(transport, "pwd", password);
+ g_free(ufrag);
+ g_free(password);
+ rexmpp_xml_add_child(content, transport);
+ int component_id;
+ rexmpp_xml_t *postponed_candidates = NULL;
+ for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) {
+ GSList *candidates = nice_agent_get_local_candidates(agent, stream_id, component_id);
+ GSList *cand_cur = candidates;
+ int cand_num = 0;
+ while (cand_cur != NULL) {
+ rexmpp_xml_t *candidate =
+ rexmpp_xml_new_elem("candidate", "urn:xmpp:jingle:transports:ice-udp:1");
+ char buf[INET6_ADDRSTRLEN];
+ NiceCandidate *c = (NiceCandidate *)cand_cur->data;
+ snprintf(buf, 11, "%u", component_id);
+ rexmpp_xml_add_attr(candidate, "component", buf);
+ rexmpp_xml_add_attr(candidate, "foundation", c->foundation);
+ rexmpp_xml_add_attr(candidate, "generation", "0");
+ char *cid = rexmpp_random_id();
+ rexmpp_xml_add_attr(candidate, "id", cid);
+ free(cid);
+ nice_address_to_string(&c->addr, buf);
+ rexmpp_xml_add_attr(candidate, "ip", buf);
+ snprintf(buf, 11, "%u", nice_address_get_port(&c->addr));
+ rexmpp_xml_add_attr(candidate, "port", buf);
+ rexmpp_xml_add_attr(candidate, "network", "0");
+ rexmpp_xml_add_attr(candidate, "protocol", "udp");
+ snprintf(buf, 11, "%u", c->priority);
+ rexmpp_xml_add_attr(candidate, "priority", buf);
+ char *nice_type[] = {"host", "srflx", "prflx", "relay"};
+ if (c->type < 4) {
+ rexmpp_xml_add_attr(candidate, "type", nice_type[c->type]);
+ }
+ /* Can't send too many candidates, since stanza sizes are usually
+ limited, and then it breaks the stream. Limiting to 10 per
+ component, sending the rest later, via transport-info. */
+ if (cand_num < 10) {
+ rexmpp_xml_add_child(transport, candidate);
+ } else {
+ rexmpp_xml_t *jingle_ti =
+ rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1");
+ rexmpp_xml_add_attr(jingle_ti, "sid", sess->sid);
+ rexmpp_xml_add_attr(jingle_ti, "action", "transport-info");
+ rexmpp_xml_t *content_copy = rexmpp_xml_clone(content);
+ rexmpp_xml_t *transport_copy = rexmpp_xml_clone(transport);
+ rexmpp_xml_add_child(jingle_ti, content_copy);
+ rexmpp_xml_add_child(content_copy, transport_copy);
+ rexmpp_xml_add_child(transport_copy, candidate);
+ jingle_ti->next = postponed_candidates;
+ postponed_candidates = jingle_ti;
+ }
+ cand_cur = cand_cur->next;
+ cand_num++;
+ }
+ if (candidates != NULL) {
+ g_slist_free_full(candidates, (GDestroyNotify)&nice_candidate_free);
+ }
+ }
+
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_new_elem("fingerprint", "urn:xmpp:jingle:apps:dtls:0");
+ rexmpp_xml_add_attr(fingerprint, "hash", "sha-256");
+ if (sess->initiator) {
+ rexmpp_xml_add_attr(fingerprint, "setup", "actpass");
+ } else if (rexmpp_jingle_dtls_is_active(sess, 1)) {
+ rexmpp_xml_add_attr(fingerprint, "setup", "active");
+ } else {
+ rexmpp_xml_add_attr(fingerprint, "setup", "passive");
+ }
+
+ rexmpp_xml_add_text(fingerprint, fp_str);
+ rexmpp_xml_add_child(transport, fingerprint);
+
+ if (sess->initiator) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ } else {
+ sess->accept = rexmpp_xml_clone(jingle);
+ rexmpp_jingle_session_configure_audio(sess);
+ rexmpp_jingle_run_audio(sess);
+ }
+
+ rexmpp_iq_new(sess->s, "set", sess->jid, jingle,
+ rexmpp_jingle_call_cb, strdup(sess->sid));
+
+ /* Now send transport-info messages with candidates that didn't fit
+ initially. */
+ while (postponed_candidates != NULL) {
+ rexmpp_xml_t *pc_next = postponed_candidates->next;
+ postponed_candidates->next = NULL;
+ rexmpp_iq_new(sess->s, "set", sess->jid, postponed_candidates,
+ rexmpp_transport_info_call_cb, strdup(sess->sid));
+ postponed_candidates = pc_next;
+ }
+}
+
+ssize_t
+rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size)
+{
+ rexmpp_jingle_component_t *comp = p;
+ rexmpp_jingle_session_t *sess = comp->session;
+ return nice_agent_send(sess->ice_agent, sess->ice_stream_id,
+ comp->component_id, size, data);
+}
+
+rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p) {
+ rexmpp_jingle_component_t *comp = p;
+ return comp->dtls;
+}
+
+/* The timeout is always zero for DTLS. */
+int rexmpp_jingle_dtls_pull_timeout_func (void *p,
+ unsigned int ms)
+{
+ rexmpp_jingle_component_t *comp = p;
+ rexmpp_jingle_session_t *sess = comp->session;
+ /* if (comp->dtls->dtls_buf_len > 0) { */
+ /* return comp->dtls->dtls_buf_len; */
+ /* } */
+
+ fd_set rfds;
+ struct timeval tv;
+
+ struct sockaddr_in cli_addr;
+ socklen_t cli_addr_size;
+ int ret;
+ char c;
+
+ FD_ZERO(&rfds);
+ int fd, nfds = -1;
+
+ GPtrArray *sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, comp->component_id);
+ guint i;
+ for (i = 0; i < sockets->len; i++) {
+ fd = g_socket_get_fd(sockets->pdata[i]);
+ FD_SET(fd, &rfds);
+ if (fd > nfds) {
+ nfds = fd;
+ }
+ }
+ g_ptr_array_unref(sockets);
+
+ tv.tv_sec = ms / 1000;
+ tv.tv_usec = (ms % 1000) * 1000;
+
+ ret = select(nfds + 1, &rfds, NULL, NULL, &tv);
+ if (ret < 0) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "DTLS pull function: select failed: %s",
+ strerror(errno));
+ return ret;
+ }
+
+ cli_addr_size = sizeof(cli_addr);
+ ret = 0;
+ sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, comp->component_id);
+ for (i = 0; i < sockets->len; i++) {
+ int err =
+ recvfrom(g_socket_get_fd(sockets->pdata[i]), &c, 1, MSG_PEEK,
+ (struct sockaddr *) &cli_addr, &cli_addr_size);
+ if (err == -1) {
+ /* ENOTCONN and EAGAIN are common here, but report other
+ errors. */
+ if (errno != ENOTCONN && errno != EAGAIN) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "DTLS pull function: failed to peek a socket: %s",
+ strerror(errno));
+ }
+ } else {
+ ret += err;
+ }
+ }
+ g_ptr_array_unref(sockets);
+
+ if (ret > 0) {
+ return ret;
+ }
+ return 0;
+}
+
+const char *rexmpp_ice_component_state_text(int state) {
+ switch (state) {
+ case NICE_COMPONENT_STATE_DISCONNECTED: return "disconnected";
+ case NICE_COMPONENT_STATE_GATHERING: return "gathering";
+ case NICE_COMPONENT_STATE_CONNECTING: return "connecting";
+ case NICE_COMPONENT_STATE_CONNECTED: return "connected";
+ case NICE_COMPONENT_STATE_READY: return "ready";
+ case NICE_COMPONENT_STATE_FAILED: return "failed";
+ case NICE_COMPONENT_STATE_LAST: return "last";
+ default: return "unknown";
+ }
+}
+
+void
+rexmpp_jingle_component_state_changed_cb (NiceAgent *agent,
+ guint stream_id,
+ guint component_id,
+ guint state,
+ gpointer data)
+{
+ rexmpp_jingle_session_t *sess = data;
+ (void)agent;
+ rexmpp_log(sess->s, LOG_DEBUG,
+ "ICE agent component %d state for stream %d changed to %s",
+ component_id, stream_id, rexmpp_ice_component_state_text(state));
+ if (component_id < 1 || component_id > 2) {
+ rexmpp_log(sess->s, LOG_CRIT, "Unexpected ICE component_id: %d",
+ component_id);
+ return;
+ }
+ if (state == NICE_COMPONENT_STATE_READY) {
+ rexmpp_log(sess->s, LOG_INFO,
+ "ICE connection established for Jingle session %s, "
+ "ICE stream %d, component %d",
+ sess->sid, stream_id, component_id);
+ if (sess->component[component_id - 1].dtls_state != REXMPP_TLS_INACTIVE) {
+ rexmpp_log(sess->s, LOG_WARNING,
+ "The connection for Jingle session %s and component %d"
+ " was established previously",
+ sess->sid, component_id);
+ return;
+ }
+
+ rexmpp_jingle_component_t *comp = &(sess->component[component_id - 1]);
+ rexmpp_dtls_connect(sess->s,
+ comp->dtls,
+ comp,
+ rexmpp_jingle_dtls_is_active(sess, 0));
+ sess->component[component_id - 1].dtls_state = REXMPP_TLS_HANDSHAKE;
+ rexmpp_tls_handshake(sess->s, comp->dtls);
+ } else if (state == NICE_COMPONENT_STATE_FAILED) {
+ rexmpp_log(sess->s, LOG_ERR,
+ "ICE connection failed for Jingle session %s, ICE stream %d, component %d",
+ sess->sid, stream_id, component_id);
+ /* todo: maybe destroy the session if it failed for all the
+ components */
+ }
+}
+
+void
+rexmpp_jingle_ice_recv_cb (NiceAgent *agent, guint stream_id, guint component_id,
+ guint len, gchar *gbuf, gpointer data)
+{
+ /* Demultiplexing here for DTLS and SRTP:
+ https://datatracker.ietf.org/doc/html/rfc5764#section-5.1.2 */
+ (void)agent;
+ (void)stream_id;
+ (void)component_id;
+ uint8_t *buf = (uint8_t *)gbuf;
+ rexmpp_jingle_component_t *comp = data;
+ if (len == 0) {
+ rexmpp_log(comp->s, LOG_WARNING, "Received an empty ICE message");
+ return;
+ }
+ if (127 < buf[0] && buf[0] < 192) {
+ int err;
+ srtp_ctx_t *srtp_in;
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ srtp_in = comp->srtp_in;
+ } else if (comp->session->component[0].dtls_state == REXMPP_TLS_ACTIVE) {
+ /* Allow to reuse the first component's DTLS handshake/SRTP
+ session. */
+ srtp_in = comp->session->component[0].srtp_in;
+ } else {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Received an SRTP packet while DTLS is inactive");
+ return;
+ }
+ if (srtp_in == NULL) {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Received an SRTP packet while SRTP is not set up");
+ return;
+ }
+ if (component_id == 1) {
+ err = srtp_unprotect(srtp_in, buf, (int*)&len);
+ if (err == srtp_err_status_auth_fail && comp->session->rtcp_mux) {
+ /* Try to demultiplex. Maybe there's a better way to do it,
+ but this will do for now. */
+ err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len);
+ }
+ } else {
+ err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len);
+ }
+ if (err) {
+ rexmpp_log(comp->s, LOG_ERR, "SRT(C)P unprotect error %d on component %d",
+ err, component_id);
+ } else {
+ /* TODO: apparently sometimes there is more than one RTCP packet
+ in the decrypted data; parse them all. */
+ uint8_t version = (buf[0] & 0xc0) >> 6;
+ if (version == 2 && len >= 12) {
+ size_t i;
+ uint8_t payload_type = buf[1] & 0x7f;
+ uint8_t csrc_count = buf[0] & 0xF;
+ uint16_t seq_num = ((uint16_t)buf[2] << 8) | buf[3];
+ int data_start_pos = 12 + csrc_count * 4;
+
+ /* Exclude possible RTCP packets here, RFC 5761 */
+ if (component_id == 1
+ && (payload_type < 64 || payload_type > 80)) {
+ if ((seq_num > comp->session->rtp_last_seq_num)
+ || (comp->session->rtp_last_seq_num > 65500)) {
+ if ((comp->session->rtp_last_seq_num <= 65500)
+ && (seq_num - comp->session->rtp_last_seq_num > 1)) {
+ rexmpp_log(comp->s, LOG_NOTICE,
+ "%d RTP packets lost",
+ seq_num - comp->session->rtp_last_seq_num - 1);
+ }
+ struct ring_buf *playback = &(comp->session->ring_buffers.playback);
+
+ /* Skip the RTP header. */
+ if (payload_type == 0) {
+ /* PCMU */
+ for (i = data_start_pos; i < len; i++) {
+ playback->buf[playback->write_pos] = rexmpp_pcmu_decode(buf[i]);
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ } else if (payload_type == 8) {
+ /* PCMA */
+ for (i = data_start_pos; i < len; i++) {
+ playback->buf[playback->write_pos] = rexmpp_pcma_decode(buf[i]);
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ }
+#ifdef HAVE_OPUS
+ else if (payload_type == comp->session->payload_type
+ && comp->session->codec == REXMPP_CODEC_OPUS) {
+ /* The same payload type as for output, which is Opus */
+ opus_int16 decoded[5760 * 2];
+ int decoded_len;
+ decoded_len =
+ opus_decode(comp->session->opus_dec,
+ (const unsigned char *)buf + data_start_pos,
+ len - data_start_pos,
+ decoded,
+ 5760,
+ 0);
+ int j;
+ for (j = 0; j < decoded_len; j++) {
+ playback->buf[playback->write_pos] = decoded[j];
+ playback->write_pos++;
+ playback->write_pos %= PA_BUF_SIZE;
+ }
+ }
+#endif /* HAVE_OPUS */
+ else {
+ /* Some other payload type, possibly with a dynamic ID */
+ rexmpp_xml_t *payload =
+ rexmpp_jingle_session_payload_by_id(comp->session,
+ payload_type);
+ const char *pl_name = rexmpp_xml_find_attr_val(payload, "name");
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Unhandled payload type %d, '%s'",
+ payload_type, pl_name);
+ }
+ comp->session->rtp_last_seq_num = seq_num;
+ } else {
+ rexmpp_log(comp->s, LOG_NOTICE,
+ "Out of order RTP packets received: %d after %d",
+ seq_num, comp->session->rtp_last_seq_num);
+ }
+ } else {
+ uint8_t packet_type = buf[1];
+ unsigned int pos = 0;
+ unsigned int rtcp_len = (buf[2] << 8) | buf[3];
+ if (packet_type == 200 && len >= 28) {
+ uint32_t rtcp_ssrc =
+ (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+ uint64_t rtcp_ntp_timestamp = 0;
+ for (i = 8; i < 16; i++) {
+ rtcp_ntp_timestamp <<= 8;
+ rtcp_ntp_timestamp |= buf[i];
+ }
+ uint32_t rtcp_rtp_timestamp =
+ (buf[16] << 24) | (buf[17] << 16) | (buf[18] << 8) | buf[19];
+ uint32_t rtcp_packet_count =
+ (buf[20] << 24) | (buf[21] << 16) | (buf[22] << 8) | buf[23];
+ uint32_t rtcp_octet_count =
+ (buf[24] << 24) | (buf[25] << 16) | (buf[26] << 8) | buf[27];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP SR received: SSRC % " PRIx32 ", NTP TS %" PRIu64
+ ", RTP TS %" PRIu32 ", PC %" PRIu32 ", OC %" PRIu32,
+ rtcp_ssrc, rtcp_ntp_timestamp, rtcp_rtp_timestamp,
+ rtcp_packet_count, rtcp_octet_count);
+ pos = 28;
+ } else if (packet_type == 201 && len >= 8) {
+ uint32_t rtcp_ssrc =
+ (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP RR received: SSRC %x",
+ rtcp_ssrc);
+ pos = 8;
+ } else {
+ rexmpp_log(comp->s, LOG_DEBUG, "RTCP packet received, PT %d",
+ packet_type);
+ }
+
+ while ((pos > 0)
+ && ((pos + 24) <= len)
+ && ((pos + 24) <= (rtcp_len + 1) * 4)) {
+ uint32_t rtcp_ssrc = (buf[pos] << 24) | (buf[pos + 1] << 16)
+ | (buf[pos + 2] << 8) | buf[pos + 3];
+ uint8_t fraction_lost = buf[pos + 4];
+ uint32_t packets_lost =
+ (buf[pos + 5] << 24) | (buf[pos + 6] << 16) | buf[pos + 7];
+ uint32_t rtcp_ehsn = (buf[pos + 8] << 24) | (buf[pos + 9] << 16)
+ | (buf[pos + 10] << 8) | buf[pos + 11];
+ uint32_t rtcp_jitter = (buf[pos + 12] << 24) | (buf[pos + 13] << 16)
+ | (buf[pos + 14] << 8) | buf[pos + 15];
+ uint32_t rtcp_lsr = (buf[pos + 16] << 24) | (buf[pos + 17] << 16)
+ | (buf[pos + 18] << 8) | buf[pos + 19];
+ uint32_t rtcp_dlsr = (buf[pos + 20] << 24) | (buf[pos + 21] << 16)
+ | (buf[pos + 22] << 8) | buf[pos + 23];
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "RTCP report block: SSRC % " PRIx32 ", lost %" PRIu32
+ "%, %" PRIu8 " packets, "
+ "highest seq num %" PRIu32
+ ", jitter %" PRIu32 ", LSR %" PRIu32 ", DLSR %" PRIu32,
+ rtcp_ssrc, fraction_lost, packets_lost,
+ rtcp_ehsn, rtcp_jitter, rtcp_lsr, rtcp_dlsr);
+ pos += 24;
+ }
+ }
+ } else {
+ rexmpp_log(comp->s, LOG_WARNING,
+ "Unhandled RT(C)P packet: version %d, length %d",
+ version, len);
+ }
+ }
+ } else {
+ rexmpp_dtls_feed(comp->s, comp->dtls, buf, len);
+ }
+}
+
+int
+rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess)
+{
+ sess->ice_agent = nice_agent_new(g_main_loop_get_context (sess->s->jingle->gloop),
+ NICE_COMPATIBILITY_RFC5245);
+ if (sess->s->local_address != NULL) {
+ NiceAddress *address = nice_address_new();
+ nice_address_set_from_string(address, sess->s->local_address);
+ nice_agent_add_local_address(sess->ice_agent, address);
+ nice_address_free(address);
+ }
+ g_object_set(sess->ice_agent, "controlling-mode", sess->initiator, NULL);
+ g_signal_connect(sess->ice_agent, "candidate-gathering-done",
+ G_CALLBACK(rexmpp_jingle_candidate_gathering_done_cb), sess);
+ g_signal_connect(sess->ice_agent, "component-state-changed",
+ G_CALLBACK(rexmpp_jingle_component_state_changed_cb), sess);
+
+ sess->ice_stream_id = nice_agent_add_stream(sess->ice_agent, sess->rtcp_mux ? 1 : 2);
+ if (sess->ice_stream_id == 0) {
+ rexmpp_log(sess->s, LOG_ERR, "Failed to add an ICE agent stream");
+ g_object_unref(sess->ice_agent);
+ sess->ice_agent = NULL;
+ return 0;
+ }
+
+ int i;
+ for (i = 0; i < (sess->rtcp_mux ? 1 : 2); i++) {
+ nice_agent_attach_recv(sess->ice_agent, sess->ice_stream_id, i + 1,
+ g_main_loop_get_context (sess->s->jingle->gloop),
+ rexmpp_jingle_ice_recv_cb,
+ &sess->component[i]);
+ }
+
+ return 1;
+}
+
+void rexmpp_jingle_turn_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) {
+ rexmpp_jingle_session_t *sess = ptr;
+ if (result != NULL && result->data != NULL) {
+ /* Only using the first address. */
+ struct in_addr addr;
+ memcpy(&addr,
+ result->data[0],
+ result->len[0]);
+ char *ip = inet_ntoa(addr);
+ rexmpp_log(s, LOG_DEBUG, "Resolved TURN server's hostname to %s (%ssecure)",
+ ip, result->secure ? "" : "in");
+ /* Adding it just for the first component for now. */
+ nice_agent_set_relay_info(sess->ice_agent, sess->ice_stream_id, 1,
+ ip, sess->turn_port,
+ sess->turn_username, sess->turn_password,
+ NICE_RELAY_TYPE_TURN_UDP);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to resolve TURN server's address");
+ }
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ rexmpp_dns_result_free(result);
+}
+
+void rexmpp_jingle_stun_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) {
+ rexmpp_jingle_session_t *sess = ptr;
+ if (result != NULL && result->data != NULL) {
+ /* Only using the first address. */
+ struct in_addr addr;
+ memcpy(&addr,
+ result->data[0],
+ result->len[0]);
+ char *ip = inet_ntoa(addr);
+ rexmpp_log(s, LOG_DEBUG, "Resolved STUN server's hostname to %s (%ssecure)",
+ ip, result->secure ? "" : "in");
+ g_object_set(sess->ice_agent, "stun-server", ip, NULL);
+ g_object_set(sess->ice_agent, "stun-server-port", sess->stun_port, NULL);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to resolve STUN server's address");
+ }
+
+ /* Proceed to TURN resolution if there's a TURN host, or just start
+ connecting. */
+ if (sess->turn_host != NULL) {
+ rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb);
+ } else {
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ }
+ rexmpp_dns_result_free(result);
+}
+
+void rexmpp_jingle_turn_cb (rexmpp_t *s,
+ void *sess_ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)req;
+ rexmpp_jingle_session_t *sess = sess_ptr;
+ if (success) {
+ /* use credentials */
+ rexmpp_xml_t *services = rexmpp_xml_first_elem_child(response);
+ if (rexmpp_xml_match(services, "urn:xmpp:extdisco:2", "services")) {
+ rexmpp_xml_t *service = rexmpp_xml_first_elem_child(services);
+ while (service != NULL) {
+ if (rexmpp_xml_match(service, "urn:xmpp:extdisco:2", "service")) {
+ const char *type = rexmpp_xml_find_attr_val(service, "type");
+ const char *transport = rexmpp_xml_find_attr_val(service, "transport");
+ const char *host = rexmpp_xml_find_attr_val(service, "host");
+ const char *port = rexmpp_xml_find_attr_val(service, "port");
+ const char *username = rexmpp_xml_find_attr_val(service, "username");
+ const char *password = rexmpp_xml_find_attr_val(service, "password");
+
+ if (sess->stun_host == NULL &&
+ type != NULL && transport != NULL && host != NULL && port != NULL &&
+ strcmp(type, "stun") == 0 && strcmp(transport, "udp") == 0) {
+ sess->stun_host = strdup(host);
+ sess->stun_port = atoi(port);
+ rexmpp_log(s, LOG_DEBUG, "Setting STUN server to %s:%s", host, port);
+ }
+
+ if (sess->turn_host == NULL &&
+ type != NULL && transport != NULL && host != NULL && port != NULL &&
+ username != NULL && password != NULL &&
+ strcmp(type, "turn") == 0 && strcmp(transport, "udp") == 0) {
+ sess->turn_host = strdup(host);
+ sess->turn_port = atoi(port);
+ sess->turn_username = strdup(username);
+ sess->turn_password = strdup(password);
+ rexmpp_log(s, LOG_DEBUG, "Setting TURN server to %s:%s", host, port);
+ }
+ }
+ service = rexmpp_xml_next_elem_sibling(service);
+ }
+ if (sess->stun_host != NULL) {
+ /* Resolve, then resolve STUN host, then connect. */
+ rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_stun_dns_cb);
+ return;
+ } else if (sess->stun_host != NULL) {
+ /* Resolve, then connect. */
+ /* todo: handle IPv6 too, but that's awkward enough for now to
+ deal with resolution before calling the library. And
+ hopefully IPv6 will make all this NAT traversal business
+ unnecessary anyway. */
+ rexmpp_dns_resolve(s, sess->stun_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb);
+ return;
+ } else {
+ rexmpp_log(s, LOG_DEBUG, "No STUN or TURN servers found");
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_DEBUG,
+ "Failed to request TURN credentials, "
+ "trying to connect without STUN/TURN");
+ }
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+}
+
+void rexmpp_jingle_discover_turn_cb (rexmpp_t *s,
+ void *sess_ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)req;
+ const char *response_from = rexmpp_xml_find_attr_val(response, "from");
+ rexmpp_jingle_session_t *sess = sess_ptr;
+ if (success) {
+ rexmpp_xml_t *services =
+ rexmpp_xml_new_elem("services", "urn:xmpp:extdisco:2");
+ rexmpp_xml_add_attr(services, "type", "turn");
+ rexmpp_iq_new(s, "get", response_from, services,
+ rexmpp_jingle_turn_cb, sess_ptr);
+ } else {
+ rexmpp_log(s, LOG_DEBUG,
+ "No external service discovery, trying to connect without STUN/TURN");
+ nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id);
+ }
+}
+
+void rexmpp_jingle_discover_turn (rexmpp_t *s, rexmpp_jingle_session_t *sess) {
+ rexmpp_disco_find_feature(s, s->initial_jid.domain, "urn:xmpp:extdisco:2",
+ rexmpp_jingle_discover_turn_cb, sess, 0, 1);
+}
+
+rexmpp_err_t
+rexmpp_jingle_call (rexmpp_t *s,
+ const char *jid)
+{
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(jid), rexmpp_random_id(),
+ REXMPP_JINGLE_SESSION_MEDIA, 1);
+ if (sess != NULL) {
+ rexmpp_jingle_ice_agent_init(sess);
+ rexmpp_jingle_discover_turn(s, sess);
+ return REXMPP_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to create a Jingle session for a call");
+ return REXMPP_E_OTHER;
+ }
+}
+
+rexmpp_err_t
+rexmpp_jingle_call_accept (rexmpp_t *s,
+ const char *sid)
+{
+ rexmpp_jingle_session_t *sess = rexmpp_jingle_session_by_id(s, sid);
+ if (sess == NULL) {
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_jingle_ice_agent_init(sess);
+
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(sess->initiate,
+ "urn:xmpp:jingle:1",
+ "content");
+ rexmpp_xml_t * ice_udp_transport =
+ rexmpp_xml_find_child(content,
+ "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport == NULL) {
+ rexmpp_log(s, LOG_ERR, "No ICE-UDP transport defined for session %s", sid);
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports", "urn:xmpp:jingle:1"),
+ "No ICE-UDP transport defined");
+ return REXMPP_E_OTHER;
+ }
+ rexmpp_jingle_ice_udp_add_remote(sess, ice_udp_transport);
+ rexmpp_jingle_discover_turn(s, sess);
+ return REXMPP_SUCCESS;
+}
+#else /* ENABLE_CALLS */
+
+ssize_t
+rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size)
+{
+ (void)p;
+ (void)data;
+ (void)size;
+ return -1;
+}
+
+rexmpp_err_t
+rexmpp_jingle_call (rexmpp_t *s,
+ const char *jid)
+{
+ (void)jid;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls");
+ return REXMPP_E_OTHER;
+}
+
+rexmpp_err_t
+rexmpp_jingle_call_accept (rexmpp_t *s,
+ const char *sid)
+{
+ (void)sid;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls");
+ return REXMPP_E_OTHER;
+}
+#endif /* ENABLE_CALLS */
+
+int rexmpp_jingle_iq (rexmpp_t *s, rexmpp_xml_t *elem) {
+ int handled = 0;
+ if (! s->enable_jingle) {
+ return handled;
+ }
+ rexmpp_xml_t *jingle =
+ rexmpp_xml_find_child(elem, "urn:xmpp:jingle:1", "jingle");
+ if (jingle != NULL) {
+ handled = 1;
+ const char *action = rexmpp_xml_find_attr_val(jingle, "action");
+ const char *sid = rexmpp_xml_find_attr_val(jingle, "sid");
+ const char *from_jid = rexmpp_xml_find_attr_val(elem, "from");
+ if (action != NULL && sid != NULL && from_jid != NULL) {
+ if (strcmp(action, "session-initiate") == 0) {
+ /* todo: could be more than one content element, handle that */
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ if (content == NULL) {
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ } else {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+
+ rexmpp_xml_t *file_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5",
+ "description");
+ rexmpp_xml_t *ibb_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1",
+ "transport");
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ rexmpp_xml_t *rtp_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:rtp:1",
+ "description");
+
+ if (file_description != NULL && ibb_transport != NULL) {
+ const char *ibb_sid = rexmpp_xml_find_attr_val(ibb_transport, "sid");
+ if (ibb_sid != NULL) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Jingle session-initiate from %s, sid %s, ibb sid %s",
+ from_jid, sid, ibb_sid);
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid),
+ REXMPP_JINGLE_SESSION_FILE, 0);
+ if (sess != NULL) {
+ sess->initiate = rexmpp_xml_clone(jingle);
+ sess->ibb_sid = strdup(ibb_sid);
+ } else {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("failed-transport",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Jingle IBB transport doesn't have a sid attribute");
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ }
+#ifdef ENABLE_CALLS
+ } else if (ice_udp_transport != NULL && rtp_description != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "Jingle session-initiate from %s, sid %s",
+ from_jid, sid);
+ rexmpp_jingle_session_t *sess =
+ rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid),
+ REXMPP_JINGLE_SESSION_MEDIA, 0);
+ if (sess != NULL) {
+ sess->rtcp_mux =
+ (rexmpp_xml_find_child(rtp_description,
+ "urn:xmpp:jingle:apps:rtp:1",
+ "rtcp-mux") != NULL);
+ sess->initiate = rexmpp_xml_clone(jingle);
+ }
+#endif /* ENABLE_CALLS */
+ } else if (file_description == NULL &&
+ rtp_description == NULL) {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-applications",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ } else if (ibb_transport == NULL &&
+ ice_udp_transport == NULL) {
+ rexmpp_jingle_session_terminate
+ (s, sid,
+ rexmpp_xml_new_elem("unsupported-transports",
+ "urn:xmpp:jingle:1"),
+ NULL);
+ } else {
+ /* todo: some other error */
+ }
+ }
+ } else if (strcmp(action, "session-terminate") == 0) {
+ /* todo: check/log the reason */
+ rexmpp_jingle_session_delete_by_id(s, sid);
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ } else if (strcmp(action, "session-accept") == 0) {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session != NULL) {
+ session->accept = rexmpp_xml_clone(jingle);
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ rexmpp_xml_t *file_description =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5",
+ "description");
+ rexmpp_xml_t *ibb_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1",
+ "transport");
+ if (ibb_transport != NULL && file_description != NULL) {
+ rexmpp_xml_t *open =
+ rexmpp_xml_new_elem("open", "http://jabber.org/protocol/ibb");
+ rexmpp_xml_add_attr(open, "sid", session->ibb_sid);
+ rexmpp_xml_add_attr(open, "block-size", "4096");
+ rexmpp_xml_add_attr(open, "stanza", "iq");
+ rexmpp_iq_new(s, "set", session->jid, open,
+ rexmpp_jingle_ibb_send_cb, strdup(sid));
+ } else {
+#ifdef ENABLE_CALLS
+ rexmpp_jingle_session_configure_audio(session);
+ rexmpp_jingle_run_audio(session);
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport != NULL) {
+ rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport);
+ } else {
+ rexmpp_log(s, LOG_WARNING,
+ "ICE-UDP transport is unset in session-accept");
+ }
+#endif /* ENABLE_CALLS */
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Jingle session %s is not found", sid);
+ }
+ } else if (strcmp(action, "transport-info") == 0) {
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid);
+ if (session != NULL) {
+#ifdef ENABLE_CALLS
+ rexmpp_xml_t *content =
+ rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content");
+ rexmpp_xml_t *ice_udp_transport =
+ rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1",
+ "transport");
+ if (ice_udp_transport != NULL) {
+ rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport);
+ }
+#endif /* ENABLE_CALLS */
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Unknown Jingle action: %s", action);
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ }
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Received a malformed Jingle element");
+ rexmpp_iq_reply(s, elem, "error",
+ rexmpp_xml_error("cancel", "bad-request"));
+ }
+ }
+
+ /* XEP-0261: Jingle In-Band Bytestreams Transport Method */
+ rexmpp_xml_t *ibb_open =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "open");
+ if (ibb_open != NULL) {
+ handled = 1;
+ /* no-op, though could check sid here. */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ }
+ rexmpp_xml_t *ibb_close =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "close");
+ if (ibb_close != NULL) {
+ handled = 1;
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ const char *sid = rexmpp_xml_find_attr_val(ibb_close, "sid");
+
+ if (sid != NULL) {
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid);
+ if (session != NULL) {
+ rexmpp_jingle_session_terminate
+ (s, session->sid,
+ rexmpp_xml_new_elem("success", "urn:xmpp:jingle:1"), NULL);
+ }
+ }
+ }
+ rexmpp_xml_t *ibb_data =
+ rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "data");
+ if (ibb_data != NULL) {
+ handled = 1;
+ const char *sid = rexmpp_xml_find_attr_val(ibb_data, "sid");
+ if (sid != NULL) {
+ rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid);
+ if (session != NULL && session->ibb_fh != NULL) {
+ char *data = NULL;
+ const char *data_base64 = rexmpp_xml_text_child(ibb_data);
+ if (data_base64 != NULL) {
+ size_t data_len = 0;
+ int base64_err = rexmpp_base64_from(data_base64, strlen(data_base64),
+ &data, &data_len);
+ if (base64_err != 0) {
+ rexmpp_log(s, LOG_ERR, "Base-64 decoding failure");
+ } else {
+ size_t written = fwrite(data, 1, data_len, session->ibb_fh);
+ if (written != data_len) {
+ rexmpp_log(s, LOG_ERR, "Wrote %d bytes, expected %d",
+ written, data_len);
+ /* todo: maybe introduce buffering, or make it an error */
+ }
+ }
+ }
+ }
+ }
+ /* todo: report errors */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ }
+ return handled;
+}
+
+int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+ int nfds = -1;
+#ifdef ENABLE_CALLS
+ gint poll_timeout;
+ GPollFD poll_fds[10];
+ GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop);
+ if (g_main_context_acquire(gctx)) {
+ gint poll_fds_n = g_main_context_query(gctx,
+ G_PRIORITY_HIGH,
+ &poll_timeout,
+ (GPollFD *)&poll_fds,
+ 10);
+ g_main_context_release(gctx);
+ int i;
+ for (i = 0; i < poll_fds_n; i++) {
+ if (poll_fds[i].events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) {
+ FD_SET(poll_fds[i].fd, read_fds);
+ }
+ if (poll_fds[i].events & (G_IO_OUT | G_IO_ERR)) {
+ FD_SET(poll_fds[i].fd, write_fds);
+ }
+ if (poll_fds[i].fd > nfds) {
+ nfds = poll_fds[i].fd;
+ }
+ }
+
+ rexmpp_jingle_session_t *sess;
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ for (i = 0; i < 2; i++) {
+ if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE &&
+ sess->component[i].dtls_state != REXMPP_TLS_CLOSED &&
+ sess->component[i].dtls_state != REXMPP_TLS_ERROR) {
+ GPtrArray *sockets =
+ nice_agent_get_sockets(sess->ice_agent,
+ sess->ice_stream_id, i + 1);
+ guint i;
+ for (i = 0; i < sockets->len; i++) {
+ int fd = g_socket_get_fd(sockets->pdata[i]);
+ FD_SET(fd, read_fds);
+ if (fd > nfds) {
+ nfds = fd;
+ }
+ }
+ g_ptr_array_unref(sockets);
+ }
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to acquire GMainContext in rexmpp_jingle_fds");
+ }
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+#endif /* ENABLE_CALLS */
+ return (nfds + 1);
+}
+
+struct timespec * rexmpp_jingle_timeout (rexmpp_t *s,
+ struct timespec *max_tv,
+ struct timespec *tv) {
+#ifdef ENABLE_CALLS
+ gint poll_timeout;
+ GPollFD poll_fds[10];
+ GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop);
+ if (g_main_context_acquire(gctx)) {
+ g_main_context_query(gctx,
+ G_PRIORITY_HIGH,
+ &poll_timeout,
+ (GPollFD *)&poll_fds,
+ 10);
+ g_main_context_release(gctx);
+
+ rexmpp_jingle_session_t *sess;
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ int i;
+ for (i = 0; i < 2; i++) {
+ if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE &&
+ sess->component[i].dtls_state != REXMPP_TLS_CLOSED &&
+ sess->component[i].dtls_state != REXMPP_TLS_ERROR) {
+ int tms = rexmpp_dtls_timeout(sess->s, sess->component[i].dtls);
+ if (tms > 0 && (poll_timeout < 0 || tms < poll_timeout)) {
+ poll_timeout = tms;
+ }
+ /* Set poll timeout to at most 5 ms if there are connected
+ components, for timely transmission of Jingle data. */
+ if (sess->component[i].dtls_state == REXMPP_TLS_ACTIVE &&
+ (poll_timeout < 0 || poll_timeout > 5)) {
+ poll_timeout = 5;
+ }
+ }
+ }
+ }
+
+ if (poll_timeout >= 0) {
+ int sec = poll_timeout / 1000;
+ int nsec = (poll_timeout % 1000) * 1000000;
+ if (max_tv == NULL ||
+ (max_tv->tv_sec > sec ||
+ (max_tv->tv_sec == sec && max_tv->tv_nsec > nsec))) {
+ tv->tv_sec = sec;
+ tv->tv_nsec = nsec;
+ max_tv = tv;
+ }
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to acquire GMainContext in rexmpp_jingle_timeout");
+ }
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)tv;
+#endif /* ENABLE_CALLS */
+ return max_tv;
+}
+
+rexmpp_err_t
+rexmpp_jingle_run (rexmpp_t *s,
+ fd_set *read_fds,
+ fd_set *write_fds)
+{
+ (void)write_fds;
+ (void)read_fds;
+#ifdef ENABLE_CALLS
+ rexmpp_jingle_session_t *sess;
+ int err;
+ unsigned char key_mat[2 * (SRTP_AES_ICM_128_KEY_LEN_WSALT)],
+ client_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT],
+ server_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT];
+ for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) {
+ char input[4096 + SRTP_MAX_TRAILER_LEN];
+ int input_len;
+ int comp_id;
+
+ for (comp_id = 0; comp_id < 2; comp_id++) {
+ rexmpp_jingle_component_t *comp = &sess->component[comp_id];
+
+ if (comp->dtls_state == REXMPP_TLS_HANDSHAKE) {
+ int ret = rexmpp_tls_handshake(s, comp->dtls);
+ if (ret == REXMPP_TLS_SUCCESS) {
+ rexmpp_log(s, LOG_DEBUG,
+ "DTLS connected for Jingle session %s, component %d",
+ sess->sid, comp->component_id);
+ comp->dtls_state = REXMPP_TLS_ACTIVE;
+
+ rexmpp_xml_t *jingle = comp->session->initiator
+ ? comp->session->accept
+ : comp->session->initiate;
+ rexmpp_xml_t *fingerprint =
+ rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (rexmpp_xml_find_child
+ (jingle, "urn:xmpp:jingle:1", "content"),
+ "urn:xmpp:jingle:transports:ice-udp:1", "transport"),
+ "urn:xmpp:jingle:apps:dtls:0", "fingerprint");
+ if (fingerprint == NULL) {
+ /* todo: might be neater to check it upon receiving the
+ stanzas, instead of checking it here */
+ rexmpp_log(comp->s, LOG_ERR,
+ "No fingerprint in the peer's Jingle element");
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "No fingerprint element");
+ return REXMPP_E_TLS;
+ } else {
+ const char *hash_str = rexmpp_xml_find_attr_val(fingerprint, "hash");
+ if (hash_str == NULL) {
+ rexmpp_log(comp->s, LOG_ERR,
+ "No hash attribute in the peer's fingerprint element");
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "No hash attribute in the fingerprint element");
+ return REXMPP_E_TLS;
+ } else {
+ char fp[64], fp_str[64 * 3];
+ size_t fp_size = 64;
+ if (rexmpp_tls_peer_fp(comp->s, comp->dtls, hash_str,
+ fp, fp_str, &fp_size))
+ {
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error",
+ "urn:xmpp:jingle:1"),
+ "Failed to obtain the DTLS certificate fingerprint");
+ return REXMPP_E_TLS;
+ } else {
+ const char *fingerprint_cont =
+ rexmpp_xml_text_child(fingerprint);
+ /* Fingerprint string should be uppercase, but
+ allowing any case for now, while Dino uses
+ lowercase. */
+ int fingerprint_mismatch = strcasecmp(fingerprint_cont, fp_str);
+ if (fingerprint_mismatch) {
+ rexmpp_log(comp->s, LOG_ERR,
+ "Peer's fingerprint mismatch: expected %s,"
+ " calculated %s",
+ fingerprint_cont, fp_str);
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("security-error", "urn:xmpp:jingle:1"),
+ "DTLS certificate fingerprint mismatch");
+ return REXMPP_E_TLS;
+ } else {
+ /* The fingerprint is fine, proceed to SRTP. */
+ rexmpp_log(comp->s, LOG_DEBUG,
+ "Peer's fingerprint: %s", fp_str);
+
+ rexmpp_tls_srtp_get_keys(s, comp->dtls,
+ SRTP_AES_128_KEY_LEN, SRTP_SALT_LEN,
+ key_mat);
+ /* client key */
+ memcpy(client_sess_key, key_mat, SRTP_AES_128_KEY_LEN);
+ /* server key */
+ memcpy(server_sess_key, key_mat + SRTP_AES_128_KEY_LEN,
+ SRTP_AES_128_KEY_LEN);
+ /* client salt */
+ memcpy(client_sess_key + SRTP_AES_128_KEY_LEN,
+ key_mat + SRTP_AES_128_KEY_LEN * 2,
+ SRTP_SALT_LEN);
+ /* server salt */
+ memcpy(server_sess_key + SRTP_AES_128_KEY_LEN,
+ key_mat + SRTP_AES_128_KEY_LEN * 2 + SRTP_SALT_LEN,
+ SRTP_SALT_LEN);
+
+ int active_role = rexmpp_jingle_dtls_is_active(sess, 0);
+
+ srtp_policy_t inbound;
+ memset(&inbound, 0x0, sizeof(srtp_policy_t));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
+ inbound.ssrc.type = ssrc_any_inbound;
+ inbound.key = active_role ? server_sess_key : client_sess_key;
+ inbound.window_size = 1024;
+ inbound.allow_repeat_tx = 1;
+ inbound.next = NULL;
+ err = srtp_create(&(comp->srtp_in), &inbound);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to create srtp_in");
+ }
+
+ srtp_policy_t outbound;
+ memset(&outbound, 0x0, sizeof(srtp_policy_t));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
+ outbound.ssrc.type = ssrc_any_outbound;
+ outbound.key = active_role ? client_sess_key : server_sess_key;
+ outbound.window_size = 1024;
+ outbound.allow_repeat_tx = 1;
+ outbound.next = NULL;
+ err = srtp_create(&(comp->srtp_out), &outbound);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "Failed to create srtp_out");
+ }
+ }
+ }
+ }
+ }
+ } else if (ret != REXMPP_TLS_E_AGAIN) {
+ rexmpp_log(s, LOG_ERR, "DTLS error for session %s, component %d",
+ sess->sid, comp->component_id);
+ comp->dtls_state = REXMPP_TLS_ERROR;
+ if (comp->component_id == 1) {
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "DTLS connection error");
+ return REXMPP_E_TLS;
+ }
+ }
+ }
+
+ /* Check on the DTLS session, too. */
+ if (comp->dtls_state == REXMPP_TLS_ACTIVE) {
+ ssize_t received;
+ rexmpp_tls_err_t err =
+ rexmpp_tls_recv(s, comp->dtls, input, 4096, &received);
+ if (err != REXMPP_TLS_SUCCESS && err != REXMPP_TLS_E_AGAIN) {
+ rexmpp_log(s, LOG_ERR,
+ "Error on rexmpp_tls_recv (component id %d), "
+ "terminating Jingle session %s",
+ comp->component_id, sess->sid);
+ rexmpp_jingle_session_terminate
+ (s, sess->sid,
+ rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"),
+ "TLS reading error");
+ return REXMPP_E_TLS;
+ }
+ }
+ }
+
+ /* Send captured audio frames for established sessions. */
+ if ((sess->component[0].dtls_state == REXMPP_TLS_ACTIVE)
+ && (sess->component[0].srtp_out != NULL)
+ && (sess->codec != REXMPP_CODEC_UNDEFINED)) {
+ struct ring_buf *capture = &(sess->ring_buffers.capture);
+ while (capture->write_pos != capture->read_pos) {
+ if (sess->codec == REXMPP_CODEC_PCMU) {
+ for (input_len = 12;
+ input_len < 4096 && capture->write_pos != capture->read_pos;
+ input_len++)
+ {
+ input[input_len] =
+ rexmpp_pcmu_encode(capture->buf[capture->read_pos]);
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ } else if (sess->codec == REXMPP_CODEC_PCMA) {
+ for (input_len = 12;
+ input_len < 4096 && capture->write_pos != capture->read_pos;
+ input_len++)
+ {
+ input[input_len] =
+ rexmpp_pcma_encode(capture->buf[capture->read_pos]);
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ }
+#ifdef HAVE_OPUS
+ else if (sess->codec == REXMPP_CODEC_OPUS) {
+ unsigned int samples_available;
+ if (capture->write_pos > capture->read_pos) {
+ samples_available = capture->write_pos - capture->read_pos;
+ } else {
+ samples_available =
+ (PA_BUF_SIZE - capture->read_pos) + capture->write_pos;
+ }
+ unsigned int frame_size = 480;
+ if (samples_available >= frame_size * 2) {
+ opus_int16 pcm[4096];
+ /* Prepare a regular buffer */
+ unsigned int i;
+ for (i = 0;
+ (i < frame_size * 2) &&
+ (capture->write_pos != capture->read_pos);
+ i++)
+ {
+ pcm[i] = capture->buf[capture->read_pos];
+ capture->read_pos++;
+ capture->read_pos %= PA_BUF_SIZE;
+ sess->rtp_timestamp++;
+ }
+ /* Encode it */
+ int encoded_len;
+ encoded_len = opus_encode(sess->opus_enc,
+ pcm,
+ frame_size,
+ (unsigned char*)input + 12,
+ 4096);
+ if (encoded_len < 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to encode an Opus frame");
+ break;
+ }
+ input_len = 12 + encoded_len;
+ } else {
+ break;
+ }
+ }
+#endif /* HAVE_OPUS */
+
+ /* Setup an RTP header */
+ uint32_t hl, nl;
+ hl = (2 << 30) /* version */
+ | (0 << 29) /* padding */
+ | (0 << 28) /* extension */
+ | (0 << 24) /* CSRC count */
+ | (0 << 23) /* marker */
+ | (sess->payload_type << 16) /* paylaod type, RFC 3551 */
+ | sess->rtp_seq_num;
+ sess->rtp_seq_num++;
+ nl = htonl(hl);
+ memcpy(input, &nl, sizeof(uint32_t));
+ nl = htonl(sess->rtp_timestamp);
+ memcpy(input + 4, &nl, sizeof(uint32_t));
+ nl = htonl(sess->rtp_ssrc);
+ memcpy(input + 8, &nl, sizeof(uint32_t));
+ /* The RTP header is ready */
+
+ srtp_ctx_t *srtp_out = sess->component[0].srtp_out;
+ err = srtp_protect(srtp_out, input, &input_len);
+ if (err) {
+ rexmpp_log(s, LOG_ERR, "SRTP protect error %d", err);
+ } else {
+ nice_agent_send(sess->ice_agent, sess->ice_stream_id,
+ /* sess->rtcp_mux ? 1 : comp->component_id, */
+ 1,
+ input_len, input);
+ }
+ }
+ }
+ }
+ g_main_context_iteration(g_main_loop_get_context(s->jingle->gloop), 0);
+#else /* ENABLE_CALLS */
+ (void)s;
+ (void)read_fds;
+#endif /* ENABLE_CALLS */
+ return REXMPP_SUCCESS;
+}