/**
* @file purple-media.c
*
* pidgin-sipe
*
* Copyright (C) 2010-2018 SIPE Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "glib.h"
#include "glib/gstdio.h"
#include
#include
#ifdef HAVE_UNISTD_H
#include
#endif
#include "sipe-common.h"
#include "mediamanager.h"
#include "agent.h"
#ifdef _WIN32
/* wrappers for write() & friends for socket handling */
#include "win32/win32dep.h"
#endif
#include "sipe-backend.h"
#include "sipe-core.h"
#include "purple-private.h"
/*
* GStreamer interfaces fail to compile on ARM architecture with -Wcast-align
*
* Diagnostic #pragma was added in GCC 4.2.0
*/
#if defined(__GNUC__)
#if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2)) || (__GNUC__ >= 5)
#if defined(__ARMEL__) || defined(__ARMEB__) || defined(__hppa__) || defined(__mips__) || defined(__sparc__) || (defined(__powerpc__) && defined(__NO_FPRS__))
#pragma GCC diagnostic ignored "-Wcast-align"
#endif
#endif
#endif
#include "media-gst.h"
#include
#include
struct sipe_backend_media {
PurpleMedia *m;
/**
* Number of media streams that were not yet locally accepted or rejected.
*/
guint unconfirmed_streams;
};
struct sipe_backend_media_stream {
gboolean local_on_hold;
gboolean remote_on_hold;
gboolean accepted;
gboolean initialized_cb_was_fired;
gulong gst_bus_cb_id;
GObject *rtpsession;
gulong on_sending_rtcp_cb_id;
};
void
sipe_backend_media_stream_free(struct sipe_backend_media_stream *stream)
{
if (stream->gst_bus_cb_id != 0) {
GstElement *pipe;
pipe = purple_media_manager_get_pipeline(
purple_media_manager_get());
if (pipe) {
GstBus *bus;
bus = gst_element_get_bus(pipe);
g_signal_handler_disconnect(bus, stream->gst_bus_cb_id);
stream->gst_bus_cb_id = 0;
gst_object_unref(bus);
}
}
if (stream->rtpsession) {
g_clear_object(&stream->rtpsession);
}
g_free(stream);
}
static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
static void
maybe_signal_stream_initialized(struct sipe_media_call *call, gchar *sessionid)
{
if (call->stream_initialized_cb) {
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, sessionid);
if (stream &&
sipe_backend_stream_initialized(call, stream) &&
!stream->backend_private->initialized_cb_was_fired) {
call->stream_initialized_cb(call, stream);
stream->backend_private->initialized_cb_was_fired = TRUE;
}
}
}
static void
on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
gchar *sessionid,
SIPE_UNUSED_PARAMETER gchar *participant,
struct sipe_media_call *call)
{
maybe_signal_stream_initialized(call, sessionid);
}
static void
on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
gchar *sessionid,
struct sipe_media_call *call)
{
maybe_signal_stream_initialized(call, sessionid);
}
static void
on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
PurpleMediaState state,
gchar *sessionid,
gchar *participant,
struct sipe_media_call *call)
{
SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
if (state == PURPLE_MEDIA_STATE_CONNECTED && sessionid && participant) {
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, sessionid);
if (stream && stream->backend_private->rtpsession &&
stream->backend_private->on_sending_rtcp_cb_id != 0) {
struct sipe_backend_media_stream *backend_stream;
SIPE_DEBUG_INFO_NOFORMAT("Peer started sending. Ceasing"
" video source requests.");
backend_stream = stream->backend_private;
g_signal_handler_disconnect(backend_stream->rtpsession,
backend_stream->on_sending_rtcp_cb_id);
g_clear_object(&backend_stream->rtpsession);
backend_stream->on_sending_rtcp_cb_id = 0;
}
} else if (state == PURPLE_MEDIA_STATE_END) {
if (sessionid && participant) {
struct sipe_media_stream *stream =
sipe_core_media_get_stream_by_id(call, sessionid);
if (stream) {
sipe_core_media_stream_end(stream);
}
} else if (!sessionid && !participant && call->media_end_cb) {
call->media_end_cb(call);
}
}
}
void
capture_pipeline(const gchar *label) {
PurpleMediaManager *manager = purple_media_manager_get();
GstElement *pipeline = purple_media_manager_get_pipeline(manager);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
}
static void
on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
struct sipe_media_call *call)
{
capture_pipeline("ERROR");
if (call->error_cb)
call->error_cb(call, message);
}
static void
on_stream_info_cb(PurpleMedia *media,
PurpleMediaInfoType type,
gchar *sessionid,
gchar *participant,
gboolean local,
struct sipe_media_call *call)
{
if (type == PURPLE_MEDIA_INFO_ACCEPT) {
if (call->call_accept_cb && !sessionid && !participant)
call->call_accept_cb(call, local);
else if (sessionid && participant) {
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, sessionid);
if (stream) {
if (!stream->backend_private->accepted && local)
--call->backend_private->unconfirmed_streams;
stream->backend_private->accepted = TRUE;
}
}
} else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
if (sessionid) {
// Hold specific stream
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, sessionid);
if (stream) {
if (local)
stream->backend_private->local_on_hold = state;
else
stream->backend_private->remote_on_hold = state;
}
} else {
// Hold all streams
GList *session_ids = purple_media_get_session_ids(media);
for (; session_ids; session_ids = session_ids->next) {
struct sipe_media_stream *stream =
sipe_core_media_get_stream_by_id(call, session_ids->data);
if (stream) {
if (local)
stream->backend_private->local_on_hold = state;
else
stream->backend_private->remote_on_hold = state;
}
}
g_list_free(session_ids);
}
if (call->call_hold_cb)
call->call_hold_cb(call, local, state);
} else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
if (!sessionid && !participant) {
if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
call->call_hangup_cb(call, local);
else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
call->call_reject_cb(call, local);
} else if (sessionid && participant) {
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, sessionid);
#ifdef HAVE_XDATA
purple_media_manager_set_application_data_callbacks(
purple_media_manager_get(), media,
sessionid, participant, NULL, NULL, NULL);
#endif
if (stream) {
if (local && --call->backend_private->unconfirmed_streams == 0 &&
call->call_reject_cb)
call->call_reject_cb(call, local);
}
}
} else if (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE) {
struct sipe_media_stream *stream =
sipe_core_media_get_stream_by_id(call, "audio");
if (stream && stream->mute_cb) {
stream->mute_cb(stream, type == PURPLE_MEDIA_INFO_MUTE);
}
}
}
static void
on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
const gchar *sessionid,
SIPE_UNUSED_PARAMETER const gchar *participant,
SIPE_UNUSED_PARAMETER PurpleMediaCandidate *local_candidate,
SIPE_UNUSED_PARAMETER PurpleMediaCandidate *remote_candidate,
struct sipe_media_call *call)
{
struct sipe_media_stream *stream =
sipe_core_media_get_stream_by_id(call, sessionid);
if (!stream) {
return;
}
#ifdef HAVE_PURPLE_NEW_TCP_ENUMS
if (purple_media_candidate_get_protocol(local_candidate) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
purple_media_set_send_rtcp_mux(media, sessionid, participant, TRUE);
}
#endif
sipe_core_media_stream_candidate_pair_established(stream);
}
struct sipe_backend_media *
sipe_backend_media_new(struct sipe_core_public *sipe_public,
struct sipe_media_call *call,
const gchar *participant,
SipeMediaCallFlags flags)
{
struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
struct sipe_backend_private *purple_private = sipe_public->backend_private;
PurpleMediaManager *manager = purple_media_manager_get();
GstElement *pipeline;
if (flags & SIPE_MEDIA_CALL_NO_UI) {
#ifdef HAVE_XDATA
media->m = purple_media_manager_create_private_media(manager,
purple_private->account, "fsrtpconference",
participant, flags & SIPE_MEDIA_CALL_INITIATOR);
#else
SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
#endif
} else {
media->m = purple_media_manager_create_media(manager,
purple_private->account, "fsrtpconference",
participant, flags & SIPE_MEDIA_CALL_INITIATOR);
}
g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
G_CALLBACK(on_candidates_prepared_cb), call);
g_signal_connect(G_OBJECT(media->m), "codecs-changed",
G_CALLBACK(on_codecs_changed_cb), call);
g_signal_connect(G_OBJECT(media->m), "stream-info",
G_CALLBACK(on_stream_info_cb), call);
g_signal_connect(G_OBJECT(media->m), "error",
G_CALLBACK(on_error_cb), call);
g_signal_connect(G_OBJECT(media->m), "state-changed",
G_CALLBACK(on_state_changed_cb), call);
g_signal_connect(G_OBJECT(media->m), "candidate-pair-established",
G_CALLBACK(on_candidate_pair_established_cb), call);
/* On error, the pipeline is no longer in PLAYING state and libpurple
* will not switch it back to PLAYING, preventing any more calls until
* application restart. We switch the state ourselves here to negate
* effect of any error in previous call (if any). */
pipeline = purple_media_manager_get_pipeline(manager);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
return media;
}
void
sipe_backend_media_free(struct sipe_backend_media *media)
{
g_free(media);
}
void
sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
{
if (media) {
guint num_params = 3;
GParameter *params = g_new0(GParameter, num_params);
params[0].name = "sdes-cname";
g_value_init(¶ms[0].value, G_TYPE_STRING);
g_value_set_string(¶ms[0].value, cname);
params[1].name = "sdes-name";
g_value_init(¶ms[1].value, G_TYPE_STRING);
params[2].name = "sdes-tool";
g_value_init(¶ms[2].value, G_TYPE_STRING);
purple_media_set_params(media->m, num_params, params);
g_value_unset(¶ms[0].value);
g_free(params);
}
}
#define FS_CODECS_CONF \
"# Automatically created by SIPE plugin\n" \
"[video/H264]\n" \
"farstream-send-profile=videoscale ! videoconvert ! fsvideoanyrate ! x264enc ! video/x-h264,profile=constrained-baseline ! rtph264pay\n" \
"\n" \
"[application/X-DATA]\n" \
"id=127\n"
static void
ensure_codecs_conf()
{
gchar *filename;
const gchar *fs_codecs_conf = FS_CODECS_CONF;
GError *error = NULL;
filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
g_file_set_contents(filename, fs_codecs_conf, strlen(fs_codecs_conf),
&error);
if (error) {
SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
error->message);
g_error_free(error);
}
g_free(filename);
}
static void
append_relay(struct sipe_backend_media_relays *relay_info, const gchar *ip,
guint port, gchar *type, gchar *username, gchar *password)
{
GstStructure *gst_relay_info;
gst_relay_info = gst_structure_new("relay-info",
"ip", G_TYPE_STRING, ip,
"port", G_TYPE_UINT, port,
"relay-type", G_TYPE_STRING, type,
"username", G_TYPE_STRING, username,
"password", G_TYPE_STRING, password,
NULL);
if (gst_relay_info) {
g_ptr_array_add((GPtrArray *)relay_info, gst_relay_info);
}
}
struct sipe_backend_media_relays *
sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
{
struct sipe_backend_media_relays *relay_info;
relay_info = (struct sipe_backend_media_relays *)
g_ptr_array_new_with_free_func((GDestroyNotify) gst_structure_free);
for (; media_relays; media_relays = media_relays->next) {\
struct sipe_media_relay *relay = media_relays->data;
/* Skip relays where IP could not be resolved. */
if (!relay->hostname)
continue;
if (relay->udp_port != 0)
append_relay(relay_info, relay->hostname, relay->udp_port,
"udp", username, password);
#ifdef HAVE_PURPLE_NEW_TCP_ENUMS
if (relay->tcp_port != 0) {
gchar *type = "tcp";
if (relay->tcp_port == 443)
type = "tls";
append_relay(relay_info, relay->hostname, relay->tcp_port,
type, username, password);
}
#endif
}
return relay_info;
}
void
sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
{
g_ptr_array_unref((GPtrArray *)media_relays);
}
#ifdef HAVE_XDATA
static void
stream_readable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
SIPE_UNUSED_PARAMETER PurpleMedia *media,
const gchar *session_id,
SIPE_UNUSED_PARAMETER const gchar *participant,
gpointer user_data)
{
struct sipe_media_call *call = (struct sipe_media_call *)user_data;
struct sipe_media_stream *stream;
SIPE_DEBUG_INFO("stream_readable_cb: %s is readable", session_id);
stream = sipe_core_media_get_stream_by_id(call, session_id);
if (stream) {
sipe_core_media_stream_readable(stream);
}
}
gssize
sipe_backend_media_stream_read(struct sipe_media_stream *stream,
guint8 *buffer, gsize len)
{
return purple_media_manager_receive_application_data(
purple_media_manager_get(),
stream->call->backend_private->m,
stream->id, stream->call->with, buffer, len, FALSE);
}
gssize
sipe_backend_media_stream_write(struct sipe_media_stream *stream,
guint8 *buffer, gsize len)
{
return purple_media_manager_send_application_data(
purple_media_manager_get(),
stream->call->backend_private->m,
stream->id, stream->call->with, buffer, len, FALSE);
}
static void
stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
SIPE_UNUSED_PARAMETER PurpleMedia *media,
const gchar *session_id,
SIPE_UNUSED_PARAMETER const gchar *participant,
gboolean writable,
gpointer user_data)
{
struct sipe_media_call *call = (struct sipe_media_call *)user_data;
struct sipe_media_stream *stream;
stream = sipe_core_media_get_stream_by_id(call, session_id);
if (!stream) {
SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
session_id);
return;
}
SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
session_id, writable ? "" : "not ");
sipe_core_media_stream_writable(stream, writable);
}
#endif
static gboolean
write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc,
guint8 payload_type)
{
GstRTCPPacket packet;
guint8 *fci_data;
if (!gst_rtcp_buffer_add_packet(buffer, GST_RTCP_TYPE_PSFB, &packet)) {
return FALSE;
}
gst_rtcp_packet_fb_set_type(&packet, GST_RTCP_PSFB_TYPE_AFB);
gst_rtcp_packet_fb_set_sender_ssrc(&packet, ssrc);
gst_rtcp_packet_fb_set_media_ssrc(&packet, SIPE_MSRTP_VSR_SOURCE_ANY);
if (!gst_rtcp_packet_fb_set_fci_length(&packet,
SIPE_MSRTP_VSR_FCI_WORDLEN)) {
gst_rtcp_packet_remove(&packet);
return FALSE;
}
fci_data = gst_rtcp_packet_fb_get_fci(&packet);
sipe_core_msrtp_write_video_source_request(fci_data, payload_type);
return TRUE;
}
static gboolean
on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject *rtpsession,
GstBuffer *buffer,
SIPE_UNUSED_PARAMETER gboolean is_early,
FsSession *fssession)
{
gboolean was_changed = FALSE;
FsCodec *send_codec;
g_object_get(fssession, "current-send-codec", &send_codec, NULL);
if (!send_codec) {
return FALSE;
}
if (sipe_strequal(send_codec->encoding_name, "H264")) {
GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT;
guint32 ssrc;
g_object_get(fssession, "ssrc", &ssrc, NULL);
gst_rtcp_buffer_map(buffer, GST_MAP_READWRITE, &rtcp_buffer);
was_changed = write_ms_h264_video_source_request(&rtcp_buffer,
ssrc, send_codec->id);
gst_rtcp_buffer_unmap(&rtcp_buffer);
}
fs_codec_destroy(send_codec);
return was_changed;
}
static GstPadProbeReturn
h264_buffer_cb(SIPE_UNUSED_PARAMETER GstPad *pad, GstPadProbeInfo *info,
SIPE_UNUSED_PARAMETER gpointer user_data)
{
GstBuffer *buffer;
GstMemory *memory;
GstMapInfo map;
guint8 *data;
guint8 nal_count = 0;
gsize pacsi_len;
buffer = gst_pad_probe_info_get_buffer(info);
// Count NALs in the buffer
gst_buffer_map(buffer, &map, GST_MAP_READ);
data = map.data;
while (data < map.data + map.size) {
guint32 size = GST_READ_UINT32_BE(data);
data += GST_READ_UINT32_BE(data) + sizeof (size);
++nal_count;
}
gst_buffer_unmap(buffer, &map);
// Write PACSI (RFC6190 section 4.9)
memory = gst_allocator_alloc(NULL, 100, NULL);
gst_memory_map(memory, &map, GST_MAP_WRITE);
pacsi_len = sipe_core_msrtp_write_video_scalability_info(map.data,
nal_count);
gst_memory_unmap(memory, &map);
gst_memory_resize(memory, 0, pacsi_len);
buffer = gst_buffer_make_writable(buffer);
gst_buffer_insert_memory(buffer, 0, memory);
GST_PAD_PROBE_INFO_DATA(info) = buffer;
return GST_PAD_PROBE_OK;
}
static gint
find_payloader(GValue *value, GstCaps *rtpcaps)
{
gint result = 1;
GstElement *element;
GstPad *sinkpad;
GstCaps *caps;
element = g_value_get_object(value);
sinkpad = gst_element_get_static_pad(element, "sink");
caps = gst_pad_query_caps(sinkpad, NULL);
/* Elements are iterated from the most downstream. We're looking for the
* first that does NOT consume RTP frames. */
result = gst_caps_can_intersect(caps, rtpcaps);
gst_caps_unref(caps);
gst_object_unref(sinkpad);
return result;
}
static void
current_send_codec_changed_cb(FsSession *fssession,
SIPE_UNUSED_PARAMETER GParamSpec *pspec,
GstBin *fsconference)
{
FsCodec *send_codec;
g_object_get(fssession, "current-send-codec", &send_codec, NULL);
if (sipe_strequal(send_codec->encoding_name, "H264")) {
guint session_id;
gchar *sendbin_name;
GstBin *sendbin;
GstCaps *caps;
GstIterator *it;
GValue val = G_VALUE_INIT;
g_object_get(fssession, "id", &session_id, NULL);
sendbin_name = g_strdup_printf("send_%u_%u", session_id,
send_codec->id);
sendbin = GST_BIN(gst_bin_get_by_name(fsconference,
sendbin_name));
g_free(sendbin_name);
if (!sendbin) {
SIPE_DEBUG_ERROR("Couldn't find Farstream send bin for "
"session %d", session_id);
return;
}
caps = gst_caps_new_empty_simple("application/x-rtp");
it = gst_bin_iterate_sorted(sendbin);
if (gst_iterator_find_custom(it, (GCompareFunc)find_payloader,
&val, caps)) {
GstElement *payloader;
GstPad *sinkpad;
payloader = g_value_get_object(&val);
sinkpad = gst_element_get_static_pad(payloader, "sink");
if (sinkpad) {
gst_pad_add_probe(sinkpad,
GST_PAD_PROBE_TYPE_BUFFER,
h264_buffer_cb, NULL, NULL);
gst_object_unref(sinkpad);
}
g_value_unset(&val);
}
gst_caps_unref(caps);
gst_iterator_free(it);
gst_object_unref(sendbin);
}
fs_codec_destroy(send_codec);
}
static gint
find_sinkpad(GValue *value, GstPad *fssession_sinkpad)
{
GstElement *tee_srcpad = g_value_get_object(value);
return !(GST_PAD_PEER(tee_srcpad) == fssession_sinkpad);
}
static void
gst_bus_cb(GstBus *bus, GstMessage *msg, struct sipe_media_stream *stream)
{
PurpleMedia *m;
const GstStructure *s;
FsSession *fssession;
GstElement *tee;
GstPad *sinkpad;
GstIterator *it;
GValue val = G_VALUE_INIT;
if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) {
return;
}
m = stream->call->backend_private->m;
s = gst_message_get_structure(msg);
if (!gst_structure_has_name(s, "farstream-codecs-changed")) {
return;
}
fssession = g_value_get_object(gst_structure_get_value(s, "session"));
g_return_if_fail(fssession);
tee = purple_media_get_tee(m, stream->id, NULL);
g_return_if_fail(tee);
g_object_get(fssession, "sink-pad", &sinkpad, NULL);
g_return_if_fail(sinkpad);
/* Check whether this message is from the FsSession we're waiting for.
* For this to be true, the tee we got from libpurple has to be linked
* to "sink-pad" of the message's FsSession. */
it = gst_element_iterate_src_pads(tee);
if (gst_iterator_find_custom(it, (GCompareFunc)find_sinkpad, &val,
sinkpad)) {
FsMediaType media_type;
if (stream->ssrc_range) {
g_object_set(fssession, "ssrc",
stream->ssrc_range->begin, NULL);
}
g_object_get(fssession, "media-type", &media_type, NULL);
if (media_type == FS_MEDIA_TYPE_VIDEO) {
GObject *rtpsession;
GstBin *fsconference;
g_object_get(fssession,
"internal-session", &rtpsession, NULL);
if (rtpsession) {
stream->backend_private->rtpsession =
gst_object_ref(rtpsession);
stream->backend_private->on_sending_rtcp_cb_id =
g_signal_connect(rtpsession,
"on-sending-rtcp",
G_CALLBACK(on_sending_rtcp_cb),
fssession);
g_object_unref (rtpsession);
}
g_object_get(fssession,
"conference", &fsconference, NULL);
g_signal_connect_object(fssession,
"notify::current-send-codec",
G_CALLBACK(current_send_codec_changed_cb),
fsconference, 0);
gst_object_unref(fsconference);
}
g_signal_handler_disconnect(bus,
stream->backend_private->gst_bus_cb_id);
stream->backend_private->gst_bus_cb_id = 0;
}
gst_iterator_free(it);
gst_object_unref(sinkpad);
}
struct sipe_backend_media_stream *
sipe_backend_media_add_stream(struct sipe_media_stream *stream,
SipeMediaType type,
SipeIceVersion ice_version,
gboolean initiator,
struct sipe_backend_media_relays *media_relays,
guint min_port, guint max_port)
{
struct sipe_backend_media *media = stream->call->backend_private;
struct sipe_backend_media_stream *backend_stream = NULL;
GstElement *pipe;
// Preallocate enough space for all potential parameters to fit.
GParameter *params = g_new0(GParameter, 6);
guint params_cnt = 0;
gchar *transmitter;
#ifdef HAVE_XDATA
PurpleMediaAppDataCallbacks callbacks = {
stream_readable_cb, stream_writable_cb
};
#endif
if (ice_version != SIPE_ICE_NO_ICE) {
transmitter = "nice";
params[params_cnt].name = "compatibility-mode";
g_value_init(¶ms[params_cnt].value, G_TYPE_UINT);
g_value_set_uint(¶ms[params_cnt].value,
ice_version == SIPE_ICE_DRAFT_6 ?
NICE_COMPATIBILITY_OC2007 :
NICE_COMPATIBILITY_OC2007R2);
++params_cnt;
if (min_port != 0) {
params[params_cnt].name = "min-port";
g_value_init(¶ms[params_cnt].value, G_TYPE_UINT);
g_value_set_uint(¶ms[params_cnt].value, min_port);
++params_cnt;
}
if (max_port != 0) {
params[params_cnt].name = "max-port";
g_value_init(¶ms[params_cnt].value, G_TYPE_UINT);
g_value_set_uint(¶ms[params_cnt].value, max_port);
++params_cnt;
}
if (media_relays) {
params[params_cnt].name = "relay-info";
g_value_init(¶ms[params_cnt].value, G_TYPE_PTR_ARRAY);
g_value_set_boxed(¶ms[params_cnt].value, media_relays);
++params_cnt;
}
if (type == SIPE_MEDIA_APPLICATION) {
params[params_cnt].name = "ice-udp";
g_value_init(¶ms[params_cnt].value, G_TYPE_BOOLEAN);
g_value_set_boolean(¶ms[params_cnt].value, FALSE);
++params_cnt;
params[params_cnt].name = "reliable";
g_value_init(¶ms[params_cnt].value, G_TYPE_BOOLEAN);
g_value_set_boolean(¶ms[params_cnt].value, TRUE);
++params_cnt;
}
} else {
// TODO: session naming here, Communicator needs audio/video
transmitter = "rawudp";
//sessionid = "sipe-voice-rawudp";
}
ensure_codecs_conf();
#ifdef HAVE_XDATA
if (type == SIPE_MEDIA_APPLICATION) {
purple_media_manager_set_application_data_callbacks(
purple_media_manager_get(),
media->m, stream->id, stream->call->with,
&callbacks, stream->call, NULL);
}
#endif
backend_stream = g_new0(struct sipe_backend_media_stream, 1);
pipe = purple_media_manager_get_pipeline(purple_media_manager_get());
if (pipe) {
GstBus *bus;
bus = gst_element_get_bus(pipe);
backend_stream->gst_bus_cb_id = g_signal_connect(bus, "message",
G_CALLBACK(gst_bus_cb), stream);
gst_object_unref(bus);
}
if (purple_media_add_stream(media->m, stream->id, stream->call->with,
sipe_media_to_purple(type),
initiator, transmitter, params_cnt,
params)) {
if (!initiator)
++media->unconfirmed_streams;
} else {
sipe_backend_media_stream_free(backend_stream);
backend_stream = NULL;
}
g_free(params);
return backend_stream;
}
void
sipe_backend_media_stream_end(struct sipe_media_call *media,
struct sipe_media_stream *stream)
{
purple_media_end(media->backend_private->m, stream->id, NULL);
}
void
sipe_backend_media_add_remote_candidates(struct sipe_media_call *media,
struct sipe_media_stream *stream,
GList *candidates)
{
GList *udp_candidates = NULL;
#ifndef HAVE_PURPLE_NEW_TCP_ENUMS
/* Keep only UDP candidates in the list to set. */
while (candidates) {
PurpleMediaCandidate *candidate = candidates->data;
PurpleMediaNetworkProtocol proto;
proto = purple_media_candidate_get_protocol(candidate);
if (proto == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP)
udp_candidates = g_list_append(udp_candidates, candidate);
candidates = candidates->next;
}
candidates = udp_candidates;
#endif
purple_media_add_remote_candidates(media->backend_private->m,
stream->id, media->with, candidates);
g_list_free(udp_candidates);
}
gboolean sipe_backend_media_is_initiator(struct sipe_media_call *media,
struct sipe_media_stream *stream)
{
return purple_media_is_initiator(media->backend_private->m,
stream ? stream->id : NULL,
stream ? media->with : NULL);
}
gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
{
return purple_media_accepted(media->m, NULL, NULL);
}
gboolean
sipe_backend_stream_initialized(struct sipe_media_call *media,
struct sipe_media_stream *stream)
{
g_return_val_if_fail(media, FALSE);
g_return_val_if_fail(stream, FALSE);
if (purple_media_candidates_prepared(media->backend_private->m,
stream->id, media->with)) {
GList *codecs;
codecs = purple_media_get_codecs(media->backend_private->m,
stream->id);
if (codecs) {
purple_media_codec_list_free(codecs);
return TRUE;
}
}
return FALSE;
}
static GList *
duplicate_tcp_candidates(GList *candidates)
{
GList *i;
GList *result = NULL;
for (i = candidates; i; i = i->next) {
PurpleMediaCandidate *candidate = i->data;
PurpleMediaNetworkProtocol protocol =
purple_media_candidate_get_protocol(candidate);
guint component_id =
purple_media_candidate_get_component_id(candidate);
if (protocol != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
PurpleMediaCandidate *c2;
if (component_id != PURPLE_MEDIA_COMPONENT_RTP) {
/* Ignore TCP candidates for other than
* the first component. */
g_object_unref(candidate);
continue;
}
c2 = purple_media_candidate_copy(candidate);
g_object_set(c2,
"component-id", PURPLE_MEDIA_COMPONENT_RTCP,
NULL);
result = g_list_append(result, c2);
}
result = g_list_append(result, candidate);
}
g_list_free(candidates);
return result;
}
GList *
sipe_backend_media_stream_get_active_local_candidates(struct sipe_media_stream *stream)
{
GList *candidates = purple_media_get_active_local_candidates(
stream->call->backend_private->m, stream->id,
stream->call->with);
return duplicate_tcp_candidates(candidates);
}
GList *
sipe_backend_media_stream_get_active_remote_candidates(struct sipe_media_stream *stream)
{
GList *candidates = purple_media_get_active_remote_candidates(
stream->call->backend_private->m, stream->id,
stream->call->with);
return duplicate_tcp_candidates(candidates);
}
#ifdef HAVE_SRTP
void
sipe_backend_media_set_encryption_keys(struct sipe_media_call *media,
struct sipe_media_stream *stream,
const guchar *encryption_key,
const guchar *decryption_key)
{
purple_media_set_encryption_parameters(media->backend_private->m,
stream->id,
"aes-128-icm",
"hmac-sha1-80",
(gchar *)encryption_key, SIPE_SRTP_KEY_LEN);
purple_media_set_decryption_parameters(media->backend_private->m,
stream->id, media->with,
"aes-128-icm",
"hmac-sha1-80",
(gchar *)decryption_key, SIPE_SRTP_KEY_LEN);
}
#else
void
sipe_backend_media_set_encryption_keys(SIPE_UNUSED_PARAMETER struct sipe_media_call *media,
SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream,
SIPE_UNUSED_PARAMETER const guchar *encryption_key,
SIPE_UNUSED_PARAMETER const guchar *decryption_key)
{}
#endif
void sipe_backend_stream_hold(struct sipe_media_call *media,
struct sipe_media_stream *stream,
gboolean local)
{
purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_HOLD,
stream->id, media->with, local);
}
void sipe_backend_stream_unhold(struct sipe_media_call *media,
struct sipe_media_stream *stream,
gboolean local)
{
purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_UNHOLD,
stream->id, media->with, local);
}
gboolean sipe_backend_stream_is_held(struct sipe_media_stream *stream)
{
g_return_val_if_fail(stream, FALSE);
return stream->backend_private->local_on_hold ||
stream->backend_private->remote_on_hold;
}
struct sipe_backend_codec *
sipe_backend_codec_new(int id, const char *name, SipeMediaType type,
guint clock_rate, guint channels)
{
PurpleMediaCodec *codec;
if (sipe_strcase_equal(name, "X-H264UC")) {
name = "H264";
}
codec = purple_media_codec_new(id, name, sipe_media_to_purple(type),
clock_rate);
g_object_set(codec, "channels", channels, NULL);
return (struct sipe_backend_codec *)codec;
}
void
sipe_backend_codec_free(struct sipe_backend_codec *codec)
{
if (codec)
g_object_unref(codec);
}
int
sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
{
return purple_media_codec_get_id((PurpleMediaCodec *)codec);
}
gchar *
sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
}
guint
sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
{
return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
}
void
sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
const gchar *name, const gchar *value)
{
purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
}
GList *
sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
{
return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
}
gboolean
sipe_backend_set_remote_codecs(struct sipe_media_call *media,
struct sipe_media_stream *stream,
GList *codecs)
{
gboolean result;
/* Lync offers multichannel audio as a codec with the same encoding name
* as the mono variant, but a different payload type and an extra
* encoding parameter:
*
* a=rtpmap:117 G722/8000/2
* a=rtpmap:9 G722/8000
*
* Since avenc_g722 from gst-libav can encode only one audio channel, ignore
* multichannel codecs we were offered by the remote host.
*/
GList *tmp = NULL;
PurpleMediaSessionType type;
for (; codecs; codecs = codecs->next) {
PurpleMediaCodec *codec = codecs->data;
g_object_get(codec, "media-type", &type, NULL);
if (type == PURPLE_MEDIA_AUDIO &&
purple_media_codec_get_channels(codec) > 1) {
continue;
}
tmp = g_list_append(tmp, codec);
}
result = purple_media_set_remote_codecs(media->backend_private->m,
stream->id, media->with,
tmp);
g_list_free(tmp);
return result;
}
GList*
sipe_backend_get_local_codecs(struct sipe_media_call *media,
struct sipe_media_stream *stream)
{
GList *codecs = purple_media_get_codecs(media->backend_private->m,
stream->id);
GList *i = codecs;
gboolean is_conference = (g_strstr_len(media->with, strlen(media->with),
"app:conf:audio-video:") != NULL);
/*
* Do not announce Theora. Its optional parameters are too long,
* Communicator rejects such SDP message and does not support the codec
* anyway.
*
* For some yet unknown reason, A/V conferencing server does not accept
* voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
* we are able to decode incoming SIREN from server and with MSOC
* client, bidirectional call using the codec works. Until resolved,
* do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
* seems to work properly in this scenario.
*/
while (i) {
PurpleMediaCodec *codec = i->data;
gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
if (sipe_strequal(encoding_name,"THEORA") ||
(is_conference && sipe_strequal(encoding_name,"SIREN"))) {
GList *tmp;
g_object_unref(codec);
tmp = i->next;
codecs = g_list_delete_link(codecs, i);
i = tmp;
} else if (sipe_strequal(encoding_name, "H264")) {
/*
* Sanitize H264 codec:
* - the encoding name must be "X-H264UC"
* - remove "sprop-parameter-sets" parameter which is
* rejected by Lync
* - add "packetization-mode" parameter if not already
* present
*/
PurpleMediaCodec *new_codec;
GList *it;
new_codec = purple_media_codec_new(
purple_media_codec_get_id(codec),
"X-H264UC",
PURPLE_MEDIA_VIDEO,
purple_media_codec_get_clock_rate(codec));
g_object_set(new_codec, "channels",
purple_media_codec_get_channels(codec),
NULL);
it = purple_media_codec_get_optional_parameters(codec);
for (; it; it = g_list_next(it)) {
PurpleKeyValuePair *pair = it->data;
if (sipe_strequal(pair->key, "sprop-parameter-sets")) {
continue;
}
purple_media_codec_add_optional_parameter(new_codec,
pair->key, pair->value);
}
if (!purple_media_codec_get_optional_parameter(new_codec,
"packetization-mode", NULL)) {
purple_media_codec_add_optional_parameter(new_codec,
"packetization-mode",
"1;mst-mode=NI-TC");
}
i->data = new_codec;
g_object_unref(codec);
} else
i = i->next;
g_free(encoding_name);
}
return codecs;
}
struct sipe_backend_candidate *
sipe_backend_candidate_new(const gchar *foundation,
SipeComponentType component,
SipeCandidateType type, SipeNetworkProtocol proto,
const gchar *ip, guint port,
const gchar *username,
const gchar *password)
{
PurpleMediaCandidate *c = purple_media_candidate_new(
/* Libnice and Farsight rely on non-NULL foundation to
* distinguish between candidates of a component. When NULL
* foundation is passed (ie. ICE draft 6 does not use foudation),
* use username instead. If no foundation is provided, Farsight
* may signal an active candidate different from the one actually
* in use. See Farsight's agent_new_selected_pair() in
* fs-nice-stream-transmitter.h where first candidate in the
* remote list is always selected when no foundation. */
foundation ? foundation : username,
component,
sipe_candidate_type_to_purple(type),
sipe_network_protocol_to_purple(proto),
ip,
port);
g_object_set(c, "username", username, "password", password, NULL);
return (struct sipe_backend_candidate *)c;
}
void
sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
{
if (candidate)
g_object_unref(candidate);
}
gchar *
sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
}
gchar *
sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
}
gchar *
sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
}
gchar *
sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
}
guint
sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
{
return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
}
gchar *
sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
{
/* Not explicitly documented, but return value must be g_free()'d */
return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
}
guint
sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
{
return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
}
guint32
sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
{
return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
}
void
sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
{
g_object_set(candidate, "priority", priority, NULL);
}
SipeComponentType
sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
{
return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
}
SipeCandidateType
sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
{
PurpleMediaCandidateType type =
purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
return purple_candidate_type_to_sipe(type);
}
SipeNetworkProtocol
sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
{
PurpleMediaNetworkProtocol proto =
purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
return purple_network_protocol_to_sipe(proto);
}
/*
* libnice can return a candidate list with duplicates. It is currently
* unknown if this is a bug in libnice or a configuration error in Skype
* for Business setups.
*
* While this is not a bug in SIPE, by removing these duplicates we make
* sure that SIPE doesn't generate incorrect SDP messages.
*/
static GList *
filter_duplicate_candidates(GList *candidates)
{
GHashTable *seen = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
GList *result = NULL;
GList *it;
for (it = candidates; it; it = it->next) {
PurpleMediaCandidate *c = it->data;
gchar *foundation = purple_media_candidate_get_foundation(c);
gchar *ip = purple_media_candidate_get_ip(c);
gchar *base_ip = purple_media_candidate_get_base_ip(c);
gchar *id = g_strdup_printf("%s %d %d %d %s %d %d %s %d",
foundation ? foundation : "-",
purple_media_candidate_get_component_id(c),
purple_media_candidate_get_protocol(c),
purple_media_candidate_get_priority(c),
ip ? ip : "-",
purple_media_candidate_get_port(c),
purple_media_candidate_get_candidate_type(c),
base_ip ? base_ip : "-",
purple_media_candidate_get_base_port(c)
);
g_free(base_ip);
g_free(ip);
g_free(foundation);
if (g_hash_table_lookup(seen, id)) {
SIPE_DEBUG_INFO("filter_duplicate_candidates: dropping '%s'",
id);
g_free(id);
g_object_unref(c);
} else {
g_hash_table_insert(seen, id, GUINT_TO_POINTER(TRUE));
result = g_list_append(result, c);
}
}
g_hash_table_destroy(seen);
g_list_free(candidates);
return result;
}
static void
remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
gpointer value,
gpointer user_data)
{
GList *entry = value;
GList **candidates = user_data;
g_object_unref(entry->data);
*candidates = g_list_delete_link(*candidates, entry);
}
static GList *
ensure_candidate_pairs(GList *candidates)
{
GHashTable *lone_cand_links;
GList *i;
lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
for (i = candidates; i; i = i->next) {
PurpleMediaCandidate *c = i->data;
gchar *foundation = purple_media_candidate_get_foundation(c);
if (g_hash_table_lookup(lone_cand_links, foundation)) {
g_hash_table_remove(lone_cand_links, foundation);
g_free(foundation);
} else {
g_hash_table_insert(lone_cand_links, foundation, i);
}
}
g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
g_hash_table_destroy(lone_cand_links);
return candidates;
}
GList *
sipe_backend_get_local_candidates(struct sipe_media_call *media,
struct sipe_media_stream *stream)
{
GList *candidates =
purple_media_get_local_candidates(media->backend_private->m,
stream->id,
media->with);
candidates = filter_duplicate_candidates(candidates);
candidates = duplicate_tcp_candidates(candidates);
/*
* Sometimes purple will not return complete list of candidates, even
* after "candidates-prepared" signal is emitted. This is a feature of
* libnice, namely affecting candidates discovered via UPnP. Nice does
* not wait until discovery is finished and can signal end of candidate
* gathering before all responses from UPnP enabled gateways are received.
*
* Remove any incomplete RTP+RTCP candidate pairs from the list.
*/
candidates = ensure_candidate_pairs(candidates);
return candidates;
}
void
sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
{
if (media)
purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
NULL, NULL, local);
}
void
sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
{
if (media)
purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
NULL, NULL, local);
}
void
sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
{
if (media)
purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
NULL, NULL, local);
}
static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
{
switch (type) {
case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
#ifdef HAVE_XDATA
case SIPE_MEDIA_APPLICATION: return PURPLE_MEDIA_APPLICATION;
#endif
default: return PURPLE_MEDIA_NONE;
}
}
static PurpleMediaCandidateType
sipe_candidate_type_to_purple(SipeCandidateType type)
{
switch (type) {
case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
}
}
static SipeCandidateType
purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
{
switch (type) {
case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
default: return SIPE_CANDIDATE_TYPE_HOST;
}
}
static PurpleMediaNetworkProtocol
sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
{
switch (proto) {
#ifdef HAVE_PURPLE_NEW_TCP_ENUMS
case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
case SIPE_NETWORK_PROTOCOL_TCP_SO:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
#else
case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
case SIPE_NETWORK_PROTOCOL_TCP_SO:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
#endif
default:
case SIPE_NETWORK_PROTOCOL_UDP:
return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
}
}
static SipeNetworkProtocol
purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
{
switch (proto) {
#ifdef HAVE_PURPLE_NEW_TCP_ENUMS
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
return SIPE_NETWORK_PROTOCOL_TCP_SO;
#else
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
#endif
default:
case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
return SIPE_NETWORK_PROTOCOL_UDP;
}
}
#ifdef HAVE_SRTP
SipeEncryptionPolicy
sipe_backend_media_get_encryption_policy(struct sipe_core_public *sipe_public)
{
PurpleAccount *account = sipe_public->backend_private->account;
const char *policy =
purple_account_get_string(account, "encryption-policy",
"obey-server");
if (sipe_strequal(policy, "disabled")) {
return SIPE_ENCRYPTION_POLICY_REJECTED;
} else if (sipe_strequal(policy, "optional")) {
return SIPE_ENCRYPTION_POLICY_OPTIONAL;
} else if (sipe_strequal(policy, "required")) {
return SIPE_ENCRYPTION_POLICY_REQUIRED;
} else {
return SIPE_ENCRYPTION_POLICY_OBEY_SERVER;
}
}
#else
SipeEncryptionPolicy
sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public)
{
return SIPE_ENCRYPTION_POLICY_REJECTED;
}
#endif
/*
Local Variables:
mode: c
c-file-style: "bsd"
indent-tabs-mode: t
tab-width: 8
End:
*/