media: add parameters for H264 video
[siplcs.git] / src / purple / purple-media.c
blobb25ea6125e95533cb269f44adbb95dce412924a0
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2016 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include "glib.h"
28 #include "glib/gstdio.h"
29 #include <fcntl.h>
30 #include <string.h>
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
35 #include "sipe-common.h"
37 #include "mediamanager.h"
38 #include "agent.h"
40 #ifdef _WIN32
41 /* wrappers for write() & friends for socket handling */
42 #include "win32/win32dep.h"
43 #endif
45 #include "sipe-backend.h"
46 #include "sipe-core.h"
48 #include "purple-private.h"
51 * GStreamer interfaces fail to compile on ARM architecture with -Wcast-align
53 * Diagnostic #pragma was added in GCC 4.2.0
55 #if defined(__GNUC__)
56 #if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2)) || (__GNUC__ >= 5)
57 #if defined(__ARMEL__) || defined(__ARMEB__) || defined(__hppa__) || defined(__mips__) || defined(__sparc__) || (defined(__powerpc__) && defined(__NO_FPRS__))
58 #pragma GCC diagnostic ignored "-Wcast-align"
59 #endif
60 #endif
61 #endif
63 #include "media-gst.h"
64 #include <gst/rtp/gstrtcpbuffer.h>
65 #include <farstream/fs-session.h>
67 struct sipe_backend_media {
68 PurpleMedia *m;
69 /**
70 * Number of media streams that were not yet locally accepted or rejected.
72 guint unconfirmed_streams;
75 struct sipe_backend_media_stream {
76 gboolean local_on_hold;
77 gboolean remote_on_hold;
78 gboolean accepted;
79 gboolean initialized_cb_was_fired;
81 gulong gst_bus_cb_id;
84 #if PURPLE_VERSION_CHECK(3,0,0)
85 #define SIPE_RELAYS_G_TYPE G_TYPE_PTR_ARRAY
86 #else
87 #define SIPE_RELAYS_G_TYPE G_TYPE_VALUE_ARRAY
88 #endif
90 void
91 sipe_backend_media_stream_free(struct sipe_backend_media_stream *stream)
93 if (stream->gst_bus_cb_id != 0) {
94 GstElement *pipe;
96 pipe = purple_media_manager_get_pipeline(
97 purple_media_manager_get());
98 if (pipe) {
99 GstBus *bus;
101 bus = gst_element_get_bus(pipe);
102 g_signal_handler_disconnect(bus, stream->gst_bus_cb_id);
103 stream->gst_bus_cb_id = 0;
104 gst_object_unref(bus);
108 g_free(stream);
111 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
112 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
113 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
114 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
115 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
117 static void
118 maybe_signal_stream_initialized(struct sipe_media_call *call, gchar *sessionid)
120 if (call->stream_initialized_cb) {
121 struct sipe_media_stream *stream;
122 stream = sipe_core_media_get_stream_by_id(call, sessionid);
124 if (sipe_backend_stream_initialized(call, stream) &&
125 !stream->backend_private->initialized_cb_was_fired) {
126 call->stream_initialized_cb(call, stream);
127 stream->backend_private->initialized_cb_was_fired = TRUE;
132 static void
133 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
134 gchar *sessionid,
135 SIPE_UNUSED_PARAMETER gchar *participant,
136 struct sipe_media_call *call)
138 maybe_signal_stream_initialized(call, sessionid);
141 static void
142 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
143 gchar *sessionid,
144 struct sipe_media_call *call)
146 maybe_signal_stream_initialized(call, sessionid);
149 static void
150 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
151 PurpleMediaState state,
152 gchar *sessionid,
153 gchar *participant,
154 struct sipe_media_call *call)
156 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
157 if (state == PURPLE_MEDIA_STATE_END) {
158 if (sessionid && participant) {
159 struct sipe_media_stream *stream =
160 sipe_core_media_get_stream_by_id(call, sessionid);
161 if (stream) {
162 sipe_core_media_stream_end(stream);
164 } else if (!sessionid && !participant && call->media_end_cb) {
165 call->media_end_cb(call);
170 void
171 capture_pipeline(const gchar *label) {
172 PurpleMediaManager *manager = purple_media_manager_get();
173 GstElement *pipeline = purple_media_manager_get_pipeline(manager);
174 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
177 static void
178 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
179 struct sipe_media_call *call)
181 capture_pipeline("ERROR");
183 if (call->error_cb)
184 call->error_cb(call, message);
187 static void
188 on_stream_info_cb(PurpleMedia *media,
189 PurpleMediaInfoType type,
190 gchar *sessionid,
191 gchar *participant,
192 gboolean local,
193 struct sipe_media_call *call)
195 if (type == PURPLE_MEDIA_INFO_ACCEPT) {
196 if (call->call_accept_cb && !sessionid && !participant)
197 call->call_accept_cb(call, local);
198 else if (sessionid && participant) {
199 struct sipe_media_stream *stream;
200 stream = sipe_core_media_get_stream_by_id(call, sessionid);
201 if (stream) {
202 if (!stream->backend_private->accepted && local)
203 --call->backend_private->unconfirmed_streams;
204 stream->backend_private->accepted = TRUE;
207 } else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
209 gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
211 if (sessionid) {
212 // Hold specific stream
213 struct sipe_media_stream *stream;
214 stream = sipe_core_media_get_stream_by_id(call, sessionid);
216 if (local)
217 stream->backend_private->local_on_hold = state;
218 else
219 stream->backend_private->remote_on_hold = state;
220 } else {
221 // Hold all streams
222 GList *session_ids = purple_media_get_session_ids(media);
224 for (; session_ids; session_ids = session_ids->next) {
225 struct sipe_media_stream *stream =
226 sipe_core_media_get_stream_by_id(call, session_ids->data);
228 if (local)
229 stream->backend_private->local_on_hold = state;
230 else
231 stream->backend_private->remote_on_hold = state;
234 g_list_free(session_ids);
237 if (call->call_hold_cb)
238 call->call_hold_cb(call, local, state);
239 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
240 if (!sessionid && !participant) {
241 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
242 call->call_hangup_cb(call, local);
243 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
244 call->call_reject_cb(call, local);
245 } else if (sessionid && participant) {
246 struct sipe_media_stream *stream;
247 stream = sipe_core_media_get_stream_by_id(call, sessionid);
249 #ifdef HAVE_XDATA
250 purple_media_manager_set_application_data_callbacks(
251 purple_media_manager_get(), media,
252 sessionid, participant, NULL, NULL, NULL);
253 #endif
255 if (stream) {
256 if (local && --call->backend_private->unconfirmed_streams == 0 &&
257 call->call_reject_cb)
258 call->call_reject_cb(call, local);
261 } else if (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE) {
262 struct sipe_media_stream *stream =
263 sipe_core_media_get_stream_by_id(call, "audio");
265 if (stream && stream->mute_cb) {
266 stream->mute_cb(stream, type == PURPLE_MEDIA_INFO_MUTE);
271 static void
272 on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
273 const gchar *sessionid,
274 SIPE_UNUSED_PARAMETER const gchar *participant,
275 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *local_candidate,
276 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *remote_candidate,
277 struct sipe_media_call *call)
279 struct sipe_media_stream *stream =
280 sipe_core_media_get_stream_by_id(call, sessionid);
282 if (!stream) {
283 return;
286 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
287 if (purple_media_candidate_get_protocol(local_candidate) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
288 purple_media_set_send_rtcp_mux(media, sessionid, participant, TRUE);
290 #endif
292 sipe_core_media_stream_candidate_pair_established(stream);
295 struct sipe_backend_media *
296 sipe_backend_media_new(struct sipe_core_public *sipe_public,
297 struct sipe_media_call *call,
298 const gchar *participant,
299 SipeMediaCallFlags flags)
301 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
302 struct sipe_backend_private *purple_private = sipe_public->backend_private;
303 PurpleMediaManager *manager = purple_media_manager_get();
304 GstElement *pipeline;
306 if (flags & SIPE_MEDIA_CALL_NO_UI) {
307 #ifdef HAVE_XDATA
308 media->m = purple_media_manager_create_private_media(manager,
309 purple_private->account, "fsrtpconference",
310 participant, flags & SIPE_MEDIA_CALL_INITIATOR);
311 #else
312 SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
313 #endif
314 } else {
315 media->m = purple_media_manager_create_media(manager,
316 purple_private->account, "fsrtpconference",
317 participant, flags & SIPE_MEDIA_CALL_INITIATOR);
320 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
321 G_CALLBACK(on_candidates_prepared_cb), call);
322 g_signal_connect(G_OBJECT(media->m), "codecs-changed",
323 G_CALLBACK(on_codecs_changed_cb), call);
324 g_signal_connect(G_OBJECT(media->m), "stream-info",
325 G_CALLBACK(on_stream_info_cb), call);
326 g_signal_connect(G_OBJECT(media->m), "error",
327 G_CALLBACK(on_error_cb), call);
328 g_signal_connect(G_OBJECT(media->m), "state-changed",
329 G_CALLBACK(on_state_changed_cb), call);
330 g_signal_connect(G_OBJECT(media->m), "candidate-pair-established",
331 G_CALLBACK(on_candidate_pair_established_cb), call);
334 /* On error, the pipeline is no longer in PLAYING state and libpurple
335 * will not switch it back to PLAYING, preventing any more calls until
336 * application restart. We switch the state ourselves here to negate
337 * effect of any error in previous call (if any). */
338 pipeline = purple_media_manager_get_pipeline(manager);
339 gst_element_set_state(pipeline, GST_STATE_PLAYING);
341 return media;
344 void
345 sipe_backend_media_free(struct sipe_backend_media *media)
347 g_free(media);
350 void
351 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
353 if (media) {
354 guint num_params = 3;
355 GParameter *params = g_new0(GParameter, num_params);
356 params[0].name = "sdes-cname";
357 g_value_init(&params[0].value, G_TYPE_STRING);
358 g_value_set_string(&params[0].value, cname);
359 params[1].name = "sdes-name";
360 g_value_init(&params[1].value, G_TYPE_STRING);
361 params[2].name = "sdes-tool";
362 g_value_init(&params[2].value, G_TYPE_STRING);
364 purple_media_set_params(media->m, num_params, params);
366 g_value_unset(&params[0].value);
367 g_free(params);
371 #define FS_CODECS_CONF \
372 "# Automatically created by SIPE plugin\n" \
373 "[application/X-DATA]\n" \
374 "id=127\n"
376 static void
377 ensure_codecs_conf()
379 gchar *filename;
380 const gchar *fs_codecs_conf = FS_CODECS_CONF;
381 GError *error = NULL;
383 filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
385 g_file_set_contents(filename, fs_codecs_conf, strlen(fs_codecs_conf),
386 &error);
387 if (error) {
388 SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
389 error->message);
390 g_error_free(error);
393 g_free(filename);
396 static void
397 append_relay(struct sipe_backend_media_relays *relay_info, const gchar *ip,
398 guint port, gchar *type, gchar *username, gchar *password)
400 GstStructure *gst_relay_info;
402 gst_relay_info = gst_structure_new("relay-info",
403 "ip", G_TYPE_STRING, ip,
404 "port", G_TYPE_UINT, port,
405 "relay-type", G_TYPE_STRING, type,
406 "username", G_TYPE_STRING, username,
407 "password", G_TYPE_STRING, password,
408 NULL);
410 if (gst_relay_info) {
411 #if PURPLE_VERSION_CHECK(3,0,0)
412 g_ptr_array_add((GPtrArray *)relay_info, gst_relay_info);
413 #else
414 GValue value;
415 memset(&value, 0, sizeof(GValue));
416 g_value_init(&value, GST_TYPE_STRUCTURE);
417 gst_value_set_structure(&value, gst_relay_info);
419 g_value_array_append((GValueArray *)relay_info, &value);
420 gst_structure_free(gst_relay_info);
421 #endif
425 struct sipe_backend_media_relays *
426 sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
428 struct sipe_backend_media_relays *relay_info;
430 relay_info = (struct sipe_backend_media_relays *)
431 #if PURPLE_VERSION_CHECK(3,0,0)
432 g_ptr_array_new_with_free_func((GDestroyNotify) gst_structure_free);
433 #else
434 g_value_array_new(0);
435 #endif
437 for (; media_relays; media_relays = media_relays->next) {\
438 struct sipe_media_relay *relay = media_relays->data;
440 /* Skip relays where IP could not be resolved. */
441 if (!relay->hostname)
442 continue;
444 if (relay->udp_port != 0)
445 append_relay(relay_info, relay->hostname, relay->udp_port,
446 "udp", username, password);
448 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
449 if (relay->tcp_port != 0) {
450 gchar *type = "tcp";
451 if (relay->tcp_port == 443)
452 type = "tls";
453 append_relay(relay_info, relay->hostname, relay->tcp_port,
454 type, username, password);
456 #endif
459 return relay_info;
462 void
463 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
465 #if !PURPLE_VERSION_CHECK(3,0,0)
466 g_value_array_free((GValueArray *)media_relays);
467 #else
468 g_ptr_array_unref((GPtrArray *)media_relays);
469 #endif
472 #ifdef HAVE_XDATA
473 static void
474 stream_readable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
475 SIPE_UNUSED_PARAMETER PurpleMedia *media,
476 const gchar *session_id,
477 SIPE_UNUSED_PARAMETER const gchar *participant,
478 gpointer user_data)
480 struct sipe_media_call *call = (struct sipe_media_call *)user_data;
481 struct sipe_media_stream *stream;
483 SIPE_DEBUG_INFO("stream_readable_cb: %s is readable", session_id);
485 stream = sipe_core_media_get_stream_by_id(call, session_id);
487 if (stream) {
488 sipe_core_media_stream_readable(stream);
492 gssize
493 sipe_backend_media_stream_read(struct sipe_media_stream *stream,
494 guint8 *buffer, gsize len)
496 return purple_media_manager_receive_application_data(
497 purple_media_manager_get(),
498 stream->call->backend_private->m,
499 stream->id, stream->call->with, buffer, len, FALSE);
502 gssize
503 sipe_backend_media_stream_write(struct sipe_media_stream *stream,
504 guint8 *buffer, gsize len)
506 return purple_media_manager_send_application_data(
507 purple_media_manager_get(),
508 stream->call->backend_private->m,
509 stream->id, stream->call->with, buffer, len, FALSE);
512 static void
513 stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
514 SIPE_UNUSED_PARAMETER PurpleMedia *media,
515 const gchar *session_id,
516 SIPE_UNUSED_PARAMETER const gchar *participant,
517 gboolean writable,
518 gpointer user_data)
520 struct sipe_media_call *call = (struct sipe_media_call *)user_data;
521 struct sipe_media_stream *stream;
523 stream = sipe_core_media_get_stream_by_id(call, session_id);
525 if (!stream) {
526 SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
527 session_id);
528 return;
531 SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
532 session_id, writable ? "" : "not ");
534 sipe_core_media_stream_writable(stream, writable);
536 #endif
538 static gboolean
539 write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc,
540 guint8 payload_type)
542 GstRTCPPacket packet;
543 guint8 *fci_data;
545 if (!gst_rtcp_buffer_add_packet(buffer, GST_RTCP_TYPE_PSFB, &packet)) {
546 return FALSE;
549 gst_rtcp_packet_fb_set_type(&packet, GST_RTCP_PSFB_TYPE_AFB);
550 gst_rtcp_packet_fb_set_sender_ssrc(&packet, ssrc);
551 gst_rtcp_packet_fb_set_media_ssrc(&packet, SIPE_MSRTP_VSR_SOURCE_ANY);
553 if (!gst_rtcp_packet_fb_set_fci_length(&packet,
554 SIPE_MSRTP_VSR_FCI_WORDLEN)) {
555 gst_rtcp_packet_remove(&packet);
556 return FALSE;
559 fci_data = gst_rtcp_packet_fb_get_fci(&packet);
561 sipe_core_msrtp_write_video_source_request(fci_data, payload_type);
563 return TRUE;
566 static gboolean
567 on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject *rtpsession,
568 GstBuffer *buffer,
569 SIPE_UNUSED_PARAMETER gboolean is_early,
570 FsSession *fssession)
572 gboolean was_changed = FALSE;
573 FsCodec *send_codec;
575 g_object_get(fssession, "current-send-codec", &send_codec, NULL);
576 if (!send_codec) {
577 return FALSE;
580 if (sipe_strequal(send_codec->encoding_name, "H264")) {
581 GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT;
582 guint32 ssrc;
584 g_object_get(fssession, "ssrc", &ssrc, NULL);
586 gst_rtcp_buffer_map(buffer, GST_MAP_READWRITE, &rtcp_buffer);
587 was_changed = write_ms_h264_video_source_request(&rtcp_buffer,
588 ssrc, send_codec->id);
589 gst_rtcp_buffer_unmap(&rtcp_buffer);
592 fs_codec_destroy(send_codec);
594 return was_changed;
597 static gint
598 find_sinkpad(GValue *value, GstPad *fssession_sinkpad)
600 GstElement *tee_srcpad = g_value_get_object(value);
602 return !(GST_PAD_PEER(tee_srcpad) == fssession_sinkpad);
605 static void
606 gst_bus_cb(GstBus *bus, GstMessage *msg, struct sipe_media_stream *stream)
608 const GstStructure *s;
609 FsSession *fssession;
610 GstElement *tee;
611 GstPad *sinkpad;
612 GstIterator *it;
613 GValue val = G_VALUE_INIT;
615 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) {
616 return;
619 s = gst_message_get_structure(msg);
620 if (!gst_structure_has_name(s, "farstream-codecs-changed")) {
621 return;
624 fssession = g_value_get_object(gst_structure_get_value(s, "session"));
625 g_return_if_fail(fssession);
627 tee = purple_media_get_tee(stream->call->backend_private->m, stream->id,
628 NULL);
629 g_return_if_fail(tee);
631 g_object_get(fssession, "sink-pad", &sinkpad, NULL);
632 g_return_if_fail(sinkpad);
634 /* Check whether this message is from the FsSession we're waiting for.
635 * For this to be true, the tee we got from libpurple has to be linked
636 * to "sink-pad" of the message's FsSession. */
637 it = gst_element_iterate_src_pads(tee);
638 if (gst_iterator_find_custom(it, (GCompareFunc)find_sinkpad, &val,
639 sinkpad)) {
640 GObject *rtpsession;
642 g_object_get(fssession, "internal-session", &rtpsession, NULL);
643 if (rtpsession) {
644 g_signal_connect(rtpsession, "on-sending-rtcp",
645 G_CALLBACK(on_sending_rtcp_cb),
646 fssession);
648 g_object_unref (rtpsession);
651 g_signal_handler_disconnect(bus,
652 stream->backend_private->gst_bus_cb_id);
653 stream->backend_private->gst_bus_cb_id = 0;
656 gst_iterator_free(it);
657 gst_object_unref(sinkpad);
660 struct sipe_backend_media_stream *
661 sipe_backend_media_add_stream(struct sipe_media_stream *stream,
662 SipeMediaType type,
663 SipeIceVersion ice_version,
664 gboolean initiator,
665 struct sipe_backend_media_relays *media_relays,
666 guint min_port, guint max_port)
668 struct sipe_backend_media *media = stream->call->backend_private;
669 struct sipe_backend_media_stream *backend_stream = NULL;
670 GstElement *pipe;
671 // Preallocate enough space for all potential parameters to fit.
672 GParameter *params = g_new0(GParameter, 6);
673 guint params_cnt = 0;
674 gchar *transmitter;
675 GValue *relay_info = NULL;
676 #ifdef HAVE_XDATA
677 PurpleMediaAppDataCallbacks callbacks = {
678 stream_readable_cb, stream_writable_cb
680 #endif
682 if (ice_version != SIPE_ICE_NO_ICE) {
683 transmitter = "nice";
685 params[params_cnt].name = "compatibility-mode";
686 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
687 g_value_set_uint(&params[params_cnt].value,
688 ice_version == SIPE_ICE_DRAFT_6 ?
689 NICE_COMPATIBILITY_OC2007 :
690 NICE_COMPATIBILITY_OC2007R2);
691 ++params_cnt;
693 if (min_port != 0) {
694 params[params_cnt].name = "min-port";
695 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
696 g_value_set_uint(&params[params_cnt].value, min_port);
697 ++params_cnt;
700 if (max_port != 0) {
701 params[params_cnt].name = "max-port";
702 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
703 g_value_set_uint(&params[params_cnt].value, max_port);
704 ++params_cnt;
707 if (media_relays) {
708 params[params_cnt].name = "relay-info";
709 g_value_init(&params[params_cnt].value, SIPE_RELAYS_G_TYPE);
710 g_value_set_boxed(&params[params_cnt].value, media_relays);
711 relay_info = &params[params_cnt].value;
712 ++params_cnt;
715 if (type == SIPE_MEDIA_APPLICATION) {
716 params[params_cnt].name = "ice-udp";
717 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
718 g_value_set_boolean(&params[params_cnt].value, FALSE);
719 ++params_cnt;
721 params[params_cnt].name = "reliable";
722 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
723 g_value_set_boolean(&params[params_cnt].value, TRUE);
724 ++params_cnt;
726 } else {
727 // TODO: session naming here, Communicator needs audio/video
728 transmitter = "rawudp";
729 //sessionid = "sipe-voice-rawudp";
732 ensure_codecs_conf();
734 #ifdef HAVE_XDATA
735 if (type == SIPE_MEDIA_APPLICATION) {
736 purple_media_manager_set_application_data_callbacks(
737 purple_media_manager_get(),
738 media->m, stream->id, stream->call->with,
739 &callbacks, stream->call, NULL);
741 #endif
743 backend_stream = g_new0(struct sipe_backend_media_stream, 1);
745 pipe = purple_media_manager_get_pipeline(purple_media_manager_get());
746 if (type == SIPE_MEDIA_VIDEO && pipe) {
747 GstBus *bus;
749 bus = gst_element_get_bus(pipe);
750 backend_stream->gst_bus_cb_id = g_signal_connect(bus, "message",
751 G_CALLBACK(gst_bus_cb), stream);
752 gst_object_unref(bus);
755 if (purple_media_add_stream(media->m, stream->id, stream->call->with,
756 sipe_media_to_purple(type),
757 initiator, transmitter, params_cnt,
758 params)) {
759 if (!initiator)
760 ++media->unconfirmed_streams;
761 } else {
762 sipe_backend_media_stream_free(backend_stream);
765 if (relay_info) {
766 g_value_unset(relay_info);
769 g_free(params);
771 return backend_stream;
774 void
775 sipe_backend_media_stream_end(struct sipe_media_call *media,
776 struct sipe_media_stream *stream)
778 purple_media_end(media->backend_private->m, stream->id, NULL);
781 void
782 sipe_backend_media_add_remote_candidates(struct sipe_media_call *media,
783 struct sipe_media_stream *stream,
784 GList *candidates)
786 GList *udp_candidates = NULL;
788 #ifndef HAVE_PURPLE_NEW_TCP_ENUMS
789 /* Keep only UDP candidates in the list to set. */
790 while (candidates) {
791 PurpleMediaCandidate *candidate = candidates->data;
792 PurpleMediaNetworkProtocol proto;
794 proto = purple_media_candidate_get_protocol(candidate);
795 if (proto == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP)
796 udp_candidates = g_list_append(udp_candidates, candidate);
798 candidates = candidates->next;
801 candidates = udp_candidates;
802 #endif
804 purple_media_add_remote_candidates(media->backend_private->m,
805 stream->id, media->with, candidates);
807 g_list_free(udp_candidates);
810 gboolean sipe_backend_media_is_initiator(struct sipe_media_call *media,
811 struct sipe_media_stream *stream)
813 return purple_media_is_initiator(media->backend_private->m,
814 stream ? stream->id : NULL,
815 stream ? media->with : NULL);
818 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
820 return purple_media_accepted(media->m, NULL, NULL);
823 gboolean
824 sipe_backend_stream_initialized(struct sipe_media_call *media,
825 struct sipe_media_stream *stream)
827 g_return_val_if_fail(media, FALSE);
828 g_return_val_if_fail(stream, FALSE);
830 if (purple_media_candidates_prepared(media->backend_private->m,
831 stream->id, media->with)) {
832 GList *codecs;
833 codecs = purple_media_get_codecs(media->backend_private->m,
834 stream->id);
835 if (codecs) {
836 purple_media_codec_list_free(codecs);
837 return TRUE;
840 return FALSE;
843 static GList *
844 duplicate_tcp_candidates(GList *candidates)
846 GList *i;
847 GList *result = NULL;
849 for (i = candidates; i; i = i->next) {
850 PurpleMediaCandidate *candidate = i->data;
851 PurpleMediaNetworkProtocol protocol =
852 purple_media_candidate_get_protocol(candidate);
853 guint component_id =
854 purple_media_candidate_get_component_id(candidate);
856 if (protocol != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
857 PurpleMediaCandidate *c2;
859 if (component_id != PURPLE_MEDIA_COMPONENT_RTP) {
860 /* Ignore TCP candidates for other than
861 * the first component. */
862 g_object_unref(candidate);
863 continue;
866 c2 = purple_media_candidate_copy(candidate);
867 g_object_set(c2,
868 "component-id", PURPLE_MEDIA_COMPONENT_RTCP,
869 NULL);
870 result = g_list_append(result, c2);
873 result = g_list_append(result, candidate);
876 g_list_free(candidates);
878 return result;
881 GList *
882 sipe_backend_media_stream_get_active_local_candidates(struct sipe_media_stream *stream)
884 GList *candidates = purple_media_get_active_local_candidates(
885 stream->call->backend_private->m, stream->id,
886 stream->call->with);
887 return duplicate_tcp_candidates(candidates);
890 GList *
891 sipe_backend_media_stream_get_active_remote_candidates(struct sipe_media_stream *stream)
893 GList *candidates = purple_media_get_active_remote_candidates(
894 stream->call->backend_private->m, stream->id,
895 stream->call->with);
896 return duplicate_tcp_candidates(candidates);
899 #ifdef HAVE_SRTP
900 void
901 sipe_backend_media_set_encryption_keys(struct sipe_media_call *media,
902 struct sipe_media_stream *stream,
903 const guchar *encryption_key,
904 const guchar *decryption_key)
906 purple_media_set_encryption_parameters(media->backend_private->m,
907 stream->id,
908 "aes-128-icm",
909 "hmac-sha1-80",
910 (gchar *)encryption_key, SIPE_SRTP_KEY_LEN);
911 purple_media_set_decryption_parameters(media->backend_private->m,
912 stream->id, media->with,
913 "aes-128-icm",
914 "hmac-sha1-80",
915 (gchar *)decryption_key, SIPE_SRTP_KEY_LEN);
917 #else
918 void
919 sipe_backend_media_set_encryption_keys(SIPE_UNUSED_PARAMETER struct sipe_media_call *media,
920 SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream,
921 SIPE_UNUSED_PARAMETER const guchar *encryption_key,
922 SIPE_UNUSED_PARAMETER const guchar *decryption_key)
924 #endif
926 void sipe_backend_stream_hold(struct sipe_media_call *media,
927 struct sipe_media_stream *stream,
928 gboolean local)
930 purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_HOLD,
931 stream->id, media->with, local);
934 void sipe_backend_stream_unhold(struct sipe_media_call *media,
935 struct sipe_media_stream *stream,
936 gboolean local)
938 purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_UNHOLD,
939 stream->id, media->with, local);
942 gboolean sipe_backend_stream_is_held(struct sipe_media_stream *stream)
944 g_return_val_if_fail(stream, FALSE);
946 return stream->backend_private->local_on_hold ||
947 stream->backend_private->remote_on_hold;
950 struct sipe_backend_codec *
951 sipe_backend_codec_new(int id, const char *name, SipeMediaType type, guint clock_rate)
953 if (sipe_strequal(name, "X-H264UC")) {
954 name = "H264";
957 return (struct sipe_backend_codec *)purple_media_codec_new(id, name,
958 sipe_media_to_purple(type),
959 clock_rate);
962 void
963 sipe_backend_codec_free(struct sipe_backend_codec *codec)
965 if (codec)
966 g_object_unref(codec);
970 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
972 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
975 gchar *
976 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
978 /* Not explicitly documented, but return value must be g_free()'d */
979 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
982 guint
983 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
985 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
988 void
989 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
990 const gchar *name, const gchar *value)
992 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
995 GList *
996 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
998 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
1001 gboolean
1002 sipe_backend_set_remote_codecs(struct sipe_media_call *media,
1003 struct sipe_media_stream *stream,
1004 GList *codecs)
1006 return purple_media_set_remote_codecs(media->backend_private->m,
1007 stream->id, media->with, codecs);
1010 GList*
1011 sipe_backend_get_local_codecs(struct sipe_media_call *media,
1012 struct sipe_media_stream *stream)
1014 GList *codecs = purple_media_get_codecs(media->backend_private->m,
1015 stream->id);
1016 GList *i = codecs;
1017 gboolean is_conference = (g_strstr_len(media->with, strlen(media->with),
1018 "app:conf:audio-video:") != NULL);
1021 * Do not announce Theora. Its optional parameters are too long,
1022 * Communicator rejects such SDP message and does not support the codec
1023 * anyway.
1025 * For some yet unknown reason, A/V conferencing server does not accept
1026 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
1027 * we are able to decode incoming SIREN from server and with MSOC
1028 * client, bidirectional call using the codec works. Until resolved,
1029 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
1030 * seems to work properly in this scenario.
1032 while (i) {
1033 PurpleMediaCodec *codec = i->data;
1034 gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
1036 if (sipe_strequal(encoding_name,"THEORA") ||
1037 (is_conference && sipe_strequal(encoding_name,"SIREN"))) {
1038 GList *tmp;
1039 g_object_unref(codec);
1040 tmp = i->next;
1041 codecs = g_list_delete_link(codecs, i);
1042 i = tmp;
1043 } else if (sipe_strequal(encoding_name, "H264")) {
1045 * Sanitize H264 codec:
1046 * - the encoding name must be "X-H264UC"
1047 * - remove "sprop-parameter-sets" parameter which is
1048 * rejected by Lync
1049 * - add "packetization-mode" parameter if not already
1050 * present
1053 PurpleMediaCodec *new_codec;
1054 GList *it;
1056 new_codec = purple_media_codec_new(
1057 purple_media_codec_get_id(codec),
1058 "X-H264UC",
1059 PURPLE_MEDIA_VIDEO,
1060 purple_media_codec_get_clock_rate(codec));
1062 g_object_set(new_codec, "channels",
1063 purple_media_codec_get_channels(codec),
1064 NULL);
1066 it = purple_media_codec_get_optional_parameters(codec);
1068 for (; it; it = g_list_next(it)) {
1069 PurpleKeyValuePair *pair = it->data;
1071 if (sipe_strequal(pair->key, "sprop-parameter-sets")) {
1072 continue;
1075 purple_media_codec_add_optional_parameter(new_codec,
1076 pair->key, pair->value);
1079 if (!purple_media_codec_get_optional_parameter(new_codec,
1080 "packetization-mode", NULL)) {
1081 purple_media_codec_add_optional_parameter(new_codec,
1082 "packetization-mode",
1083 "1;mst-mode=NI-TC");
1086 i->data = new_codec;
1088 g_object_unref(codec);
1089 } else
1090 i = i->next;
1092 g_free(encoding_name);
1095 return codecs;
1098 struct sipe_backend_candidate *
1099 sipe_backend_candidate_new(const gchar *foundation,
1100 SipeComponentType component,
1101 SipeCandidateType type, SipeNetworkProtocol proto,
1102 const gchar *ip, guint port,
1103 const gchar *username,
1104 const gchar *password)
1106 PurpleMediaCandidate *c = purple_media_candidate_new(
1107 /* Libnice and Farsight rely on non-NULL foundation to
1108 * distinguish between candidates of a component. When NULL
1109 * foundation is passed (ie. ICE draft 6 does not use foudation),
1110 * use username instead. If no foundation is provided, Farsight
1111 * may signal an active candidate different from the one actually
1112 * in use. See Farsight's agent_new_selected_pair() in
1113 * fs-nice-stream-transmitter.h where first candidate in the
1114 * remote list is always selected when no foundation. */
1115 foundation ? foundation : username,
1116 component,
1117 sipe_candidate_type_to_purple(type),
1118 sipe_network_protocol_to_purple(proto),
1120 port);
1121 g_object_set(c, "username", username, "password", password, NULL);
1122 return (struct sipe_backend_candidate *)c;
1125 void
1126 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
1128 if (candidate)
1129 g_object_unref(candidate);
1132 gchar *
1133 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
1135 /* Not explicitly documented, but return value must be g_free()'d */
1136 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
1139 gchar *
1140 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
1142 /* Not explicitly documented, but return value must be g_free()'d */
1143 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
1146 gchar *
1147 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
1149 /* Not explicitly documented, but return value must be g_free()'d */
1150 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
1153 gchar *
1154 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
1156 /* Not explicitly documented, but return value must be g_free()'d */
1157 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
1160 guint
1161 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
1163 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
1166 gchar *
1167 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
1169 /* Not explicitly documented, but return value must be g_free()'d */
1170 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
1173 guint
1174 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
1176 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
1179 guint32
1180 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
1182 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
1185 void
1186 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
1188 g_object_set(candidate, "priority", priority, NULL);
1191 SipeComponentType
1192 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
1194 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
1197 SipeCandidateType
1198 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
1200 PurpleMediaCandidateType type =
1201 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
1202 return purple_candidate_type_to_sipe(type);
1205 SipeNetworkProtocol
1206 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
1208 PurpleMediaNetworkProtocol proto =
1209 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
1210 return purple_network_protocol_to_sipe(proto);
1213 static void
1214 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
1215 gpointer value,
1216 gpointer user_data)
1218 GList *entry = value;
1219 GList **candidates = user_data;
1221 g_object_unref(entry->data);
1222 *candidates = g_list_delete_link(*candidates, entry);
1225 static GList *
1226 ensure_candidate_pairs(GList *candidates)
1228 GHashTable *lone_cand_links;
1229 GList *i;
1231 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1233 for (i = candidates; i; i = i->next) {
1234 PurpleMediaCandidate *c = i->data;
1235 gchar *foundation = purple_media_candidate_get_foundation(c);
1237 if (g_hash_table_lookup(lone_cand_links, foundation)) {
1238 g_hash_table_remove(lone_cand_links, foundation);
1239 g_free(foundation);
1240 } else {
1241 g_hash_table_insert(lone_cand_links, foundation, i);
1245 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
1246 g_hash_table_destroy(lone_cand_links);
1248 return candidates;
1251 GList *
1252 sipe_backend_get_local_candidates(struct sipe_media_call *media,
1253 struct sipe_media_stream *stream)
1255 GList *candidates =
1256 purple_media_get_local_candidates(media->backend_private->m,
1257 stream->id,
1258 media->with);
1259 candidates = duplicate_tcp_candidates(candidates);
1262 * Sometimes purple will not return complete list of candidates, even
1263 * after "candidates-prepared" signal is emitted. This is a feature of
1264 * libnice, namely affecting candidates discovered via UPnP. Nice does
1265 * not wait until discovery is finished and can signal end of candidate
1266 * gathering before all responses from UPnP enabled gateways are received.
1268 * Remove any incomplete RTP+RTCP candidate pairs from the list.
1270 candidates = ensure_candidate_pairs(candidates);
1271 return candidates;
1274 void
1275 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
1277 if (media)
1278 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
1279 NULL, NULL, local);
1282 void
1283 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
1285 if (media)
1286 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
1287 NULL, NULL, local);
1290 void
1291 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
1293 if (media)
1294 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
1295 NULL, NULL, local);
1298 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
1300 switch (type) {
1301 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
1302 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
1303 #ifdef HAVE_XDATA
1304 case SIPE_MEDIA_APPLICATION: return PURPLE_MEDIA_APPLICATION;
1305 #endif
1306 default: return PURPLE_MEDIA_NONE;
1310 static PurpleMediaCandidateType
1311 sipe_candidate_type_to_purple(SipeCandidateType type)
1313 switch (type) {
1314 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
1315 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
1316 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
1317 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
1318 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
1322 static SipeCandidateType
1323 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
1325 switch (type) {
1326 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
1327 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
1328 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
1329 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
1330 default: return SIPE_CANDIDATE_TYPE_HOST;
1334 static PurpleMediaNetworkProtocol
1335 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
1337 switch (proto) {
1338 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1339 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1340 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
1341 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1342 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
1343 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1344 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
1345 #else
1346 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1347 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1348 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1349 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
1350 #endif
1351 default:
1352 case SIPE_NETWORK_PROTOCOL_UDP:
1353 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
1357 static SipeNetworkProtocol
1358 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
1360 switch (proto) {
1361 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1362 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
1363 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1364 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
1365 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
1366 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
1367 return SIPE_NETWORK_PROTOCOL_TCP_SO;
1368 #else
1369 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
1370 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1371 #endif
1372 default:
1373 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
1374 return SIPE_NETWORK_PROTOCOL_UDP;
1378 #ifdef HAVE_SRTP
1379 SipeEncryptionPolicy
1380 sipe_backend_media_get_encryption_policy(struct sipe_core_public *sipe_public)
1382 PurpleAccount *account = sipe_public->backend_private->account;
1384 const char *policy =
1385 purple_account_get_string(account, "encryption-policy",
1386 "obey-server");
1388 if (sipe_strequal(policy, "disabled")) {
1389 return SIPE_ENCRYPTION_POLICY_REJECTED;
1390 } else if (sipe_strequal(policy, "optional")) {
1391 return SIPE_ENCRYPTION_POLICY_OPTIONAL;
1392 } else if (sipe_strequal(policy, "required")) {
1393 return SIPE_ENCRYPTION_POLICY_REQUIRED;
1394 } else {
1395 return SIPE_ENCRYPTION_POLICY_OBEY_SERVER;
1398 #else
1399 SipeEncryptionPolicy
1400 sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public)
1402 return SIPE_ENCRYPTION_POLICY_REJECTED;
1404 #endif
1407 Local Variables:
1408 mode: c
1409 c-file-style: "bsd"
1410 indent-tabs-mode: t
1411 tab-width: 8
1412 End: