Statistics
| Branch: | Revision:

janus-gateway / sdp.c @ a972337c

History | View | Annotate | Download (51 KB)

1
/*! \file    sdp.c
2
 * \author   Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief    SDP processing
5
 * \details  Implementation (based on the Sofia-SDP stack) of the SDP
6
 * parser/merger/generator in the gateway. Each SDP coming from peers is
7
 * stripped/anonymized before it is passed to the plugins: all
8
 * DTLS/ICE/transport related information is removed, only leaving the
9
 * relevant information in place. SDP coming from plugins is stripped/anonymized
10
 * as well, and merged with the proper DTLS/ICE/transport information before
11
 * it is sent to the peers.
12
 * 
13
 * \ingroup protocols
14
 * \ref protocols
15
 */
16
 
17
#include "janus.h"
18
#include "ice.h"
19
#include "dtls.h"
20
#include "sdp.h"
21
#include "utils.h"
22
#include "debug.h"
23

    
24

    
25
static su_home_t *home = NULL;
26

    
27

    
28
/* SDP Initialization */
29
int janus_sdp_init(void) {
30
        home = su_home_new(sizeof(su_home_t));
31
        if(su_home_init(home) < 0) {
32
                JANUS_LOG(LOG_FATAL, "Ops, error setting up sofia-sdp?\n");
33
                return -1;
34
        }
35
        return 0;
36
}
37

    
38
void janus_sdp_deinit(void) {
39
        su_home_deinit(home);
40
        su_home_unref(home);
41
        home = NULL;
42
}
43

    
44

    
45
/* Fake attribute we use for the sendrecv hack */
46
static sdp_attribute_t fakedir = {
47
        .a_size = sizeof(sdp_attribute_t),
48
        .a_name = "jfmod",
49
        .a_value = "sr"
50
};
51

    
52

    
53
/* SDP parser */
54
void janus_sdp_free(janus_sdp *sdp) {
55
        if(!sdp)
56
                return;
57
        sdp_parser_t *parser = (sdp_parser_t *)sdp->parser;
58
        if(parser)
59
                sdp_parser_free(parser);
60
        sdp->parser = NULL;
61
        sdp->sdp = NULL;
62
        g_free(sdp);
63
        sdp = NULL;
64
}
65

    
66
/* Pre-parse SDP: is this SDP valid? how many audio/video lines? any features to take into account? */
67
janus_sdp *janus_sdp_preparse(const char *jsep_sdp, int *audio, int *video, int *data, int *bundle, int *rtcpmux, int *trickle) {
68
        if(!jsep_sdp || !audio || !video || !data || !bundle || !rtcpmux || !trickle) {
69
                JANUS_LOG(LOG_ERR, "  Can't preparse, invalid arduments\n");
70
                return NULL;
71
        }
72
        sdp_parser_t *parser = sdp_parse(home, jsep_sdp, strlen(jsep_sdp), 0);
73
        sdp_session_t *parsed_sdp = sdp_session(parser);
74
        if(!parsed_sdp) {
75
                JANUS_LOG(LOG_ERR, "  Error parsing SDP? %s\n", sdp_parsing_error(parser));
76
                sdp_parser_free(parser);
77
                /* Invalid SDP */
78
                return NULL;
79
        }
80
        sdp_media_t *m = parsed_sdp->sdp_media;
81
        while(m) {
82
                if(m->m_type == sdp_media_audio && m->m_port > 0) {
83
                        *audio = *audio + 1;
84
                } else if(m->m_type == sdp_media_video && m->m_port > 0) {
85
                        *video = *video + 1;
86
                }
87
                m = m->m_next;
88
        }
89
#ifdef HAVE_SCTP
90
        *data = (strstr(jsep_sdp, "DTLS/SCTP") && !strstr(jsep_sdp, " 0 DTLS/SCTP")) ? 1 : 0;        /* FIXME This is a really hacky way of checking... */
91
#else
92
        *data = 0;
93
#endif
94
        *bundle = strstr(jsep_sdp, "a=group:BUNDLE") ? 1 : 0;        /* FIXME This is a really hacky way of checking... */
95
        *rtcpmux = strstr(jsep_sdp, "a=rtcp-mux") ? 1 : 0;        /* FIXME Should we make this check per-medium? */
96
        //~ *trickle = (strstr(jsep_sdp, "trickle") || strstr(jsep_sdp, "google-ice") || strstr(jsep_sdp, "Mozilla")) ? 1 : 0;        /* FIXME This is a really hacky way of checking... */
97
        /* FIXME We're assuming trickle is always supported, see https://github.com/meetecho/janus-gateway/issues/83 */
98
        *trickle = 1;
99
        janus_sdp *sdp = (janus_sdp *)g_malloc0(sizeof(janus_sdp));
100
        if(sdp == NULL) {
101
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
102
                return NULL;
103
        }
104

    
105
        sdp->parser = parser;
106
        sdp->sdp = parsed_sdp;
107
        return sdp;
108
}
109

    
110
/* Parse SDP */
111
int janus_sdp_parse(janus_ice_handle *handle, janus_sdp *sdp) {
112
        if(!handle || !sdp)
113
                return -1;
114
        sdp_session_t *remote_sdp = (sdp_session_t *)sdp->sdp;
115
        if(!remote_sdp)
116
                return -1;
117
        janus_ice_stream *stream = NULL;
118
        gchar *ruser = NULL, *rpass = NULL, *rhashing = NULL, *rfingerprint = NULL;
119
        int audio = 0, video = 0;
120
#ifdef HAVE_SCTP
121
        int data = 0;
122
#endif
123
        /* Ok, let's start */
124
        sdp_attribute_t *a = remote_sdp->sdp_attributes;
125
        while(a) {
126
                if(a->a_name) {
127
                        if(!strcasecmp(a->a_name, "fingerprint")) {
128
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint (global) : %s\n", handle->handle_id, a->a_value);
129
                                if(strcasestr(a->a_value, "sha-256 ") == a->a_value) {
130
                                        rhashing = g_strdup("sha-256");
131
                                        rfingerprint = g_strdup(a->a_value + strlen("sha-256 "));
132
                                } else if(strcasestr(a->a_value, "sha-1 ") == a->a_value) {
133
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"]  Hashing algorithm not the one we expected (sha-1 instead of sha-256), but that's ok\n", handle->handle_id);
134
                                        rhashing = g_strdup("sha-1");
135
                                        rfingerprint = g_strdup(a->a_value + strlen("sha-1 "));
136
                                } else {
137
                                        /* FIXME We should handle this somehow anyway... OpenSSL supports them all */
138
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"]  Hashing algorithm not the one we expected (sha-256/sha-1), *NOT* cool\n", handle->handle_id);
139
                                }
140
                        } else if(!strcasecmp(a->a_name, "ice-ufrag")) {
141
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE ufrag (global):   %s\n", handle->handle_id, a->a_value);
142
                                ruser = g_strdup(a->a_value);
143
                        } else if(!strcasecmp(a->a_name, "ice-pwd")) {
144
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE pwd (global):     %s\n", handle->handle_id, a->a_value);
145
                                rpass = g_strdup(a->a_value);
146
                        }
147
                }
148
                a = a->a_next;
149
        }
150
        sdp_media_t *m = remote_sdp->sdp_media;
151
        while(m) {
152
                /* What media type is this? */
153
                stream = NULL;
154
                if(m->m_type == sdp_media_audio) {
155
                        if(handle->rtp_profile == NULL && m->m_proto_name != NULL)
156
                                handle->rtp_profile = g_strdup(m->m_proto_name);
157
                        if(m->m_port > 0) {
158
                                audio++;
159
                                if(audio > 1) {
160
                                        m = m->m_next;
161
                                        continue;
162
                                }
163
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing audio candidates (stream=%d)...\n", handle->handle_id, handle->audio_id);
164
                                stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->audio_id));
165
                        } else {
166
                                /* Audio rejected? */
167
                                janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_AUDIO);
168
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio rejected by peer...\n", handle->handle_id);
169
                        }
170
                } else if(m->m_type == sdp_media_video) {
171
                        if(handle->rtp_profile == NULL && m->m_proto_name != NULL)
172
                                handle->rtp_profile = g_strdup(m->m_proto_name);
173
                        if(m->m_port > 0) {
174
                                video++;
175
                                if(video > 1) {
176
                                        m = m->m_next;
177
                                        continue;
178
                                }
179
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing video candidates (stream=%d)...\n", handle->handle_id, handle->video_id);
180
                                if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) {
181
                                        stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->video_id));
182
                                } else {
183
                                        gint id = handle->audio_id > 0 ? handle->audio_id : handle->video_id;
184
                                        stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
185
                                }
186
                        } else {
187
                                /* Video rejected? */
188
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video rejected by peer...\n", handle->handle_id);
189
                                janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO);
190
                        }
191
#ifdef HAVE_SCTP
192
                } else if(m->m_type == sdp_media_application) {
193
                        /* Is this SCTP for DataChannels? */
194
                        if(m->m_proto_name != NULL && !strcasecmp(m->m_proto_name, "DTLS/SCTP")) {
195
                                if(m->m_port > 0) {
196
                                        /* Yep */
197
                                        data++;
198
                                        if(data > 1) {
199
                                                m = m->m_next;
200
                                                continue;
201
                                        }
202
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsing SCTP candidates (stream=%d)...\n", handle->handle_id, handle->video_id);
203
                                        if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) {
204
                                                stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->data_id));
205
                                        } else {
206
                                                gint id = handle->audio_id > 0 ? handle->audio_id : (handle->video_id > 0 ? handle->video_id : handle->data_id);
207
                                                stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
208
                                        }
209
                                        if(stream == NULL) {
210
                                                JANUS_LOG(LOG_WARN, "No valid stream for data??\n");
211
                                                continue;
212
                                        }
213
                                }
214
                        } else {
215
                                /* Data channels rejected? */
216
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data channels rejected by peer...\n", handle->handle_id);
217
                                janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_DATA_CHANNELS);
218
                        }
219
#endif
220
                } else {
221
                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id);
222
                }
223
                if(stream == NULL) {
224
                        m = m->m_next;
225
                        continue;
226
                }
227
                /* Look for mid, ICE credentials and fingerprint first: check media attributes */
228
                a = m->m_attributes;
229
                while(a) {
230
                        if(a->a_name) {
231
                                if(!strcasecmp(a->a_name, "mid")) {
232
                                        /* Found mid attribute */
233
                                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
234
                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Audio mid: %s\n", handle->handle_id, a->a_value);
235
                                                handle->audio_mid = g_strdup(a->a_value);
236
                                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
237
                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Video mid: %s\n", handle->handle_id, a->a_value);
238
                                                handle->video_mid = g_strdup(a->a_value);
239
#ifdef HAVE_SCTP
240
                                        } else if(m->m_type == sdp_media_application) {
241
                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] Data Channel mid: %s\n", handle->handle_id, a->a_value);
242
                                                handle->data_mid = g_strdup(a->a_value);
243
#endif
244
                                        }
245
                                } else if(!strcasecmp(a->a_name, "fingerprint")) {
246
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Fingerprint (local) : %s\n", handle->handle_id, a->a_value);
247
                                        if(strcasestr(a->a_value, "sha-256 ") == a->a_value) {
248
                                                if(rhashing)
249
                                                        g_free(rhashing);        /* FIXME We're overwriting the global one, if any */
250
                                                rhashing = g_strdup("sha-256");
251
                                                if(rfingerprint)
252
                                                        g_free(rfingerprint);        /* FIXME We're overwriting the global one, if any */
253
                                                rfingerprint = g_strdup(a->a_value + strlen("sha-256 "));
254
                                        } else if(strcasestr(a->a_value, "sha-1 ") == a->a_value) {
255
                                                JANUS_LOG(LOG_WARN, "[%"SCNu64"]  Hashing algorithm not the one we expected (sha-1 instead of sha-256), but that's ok\n", handle->handle_id);
256
                                                if(rhashing)
257
                                                        g_free(rhashing);        /* FIXME We're overwriting the global one, if any */
258
                                                rhashing = g_strdup("sha-1");
259
                                                if(rfingerprint)
260
                                                        g_free(rfingerprint);        /* FIXME We're overwriting the global one, if any */
261
                                                rfingerprint = g_strdup(a->a_value + strlen("sha-1 "));
262
                                        } else {
263
                                                /* FIXME We should handle this somehow anyway... OpenSSL supports them all */
264
                                                JANUS_LOG(LOG_WARN, "[%"SCNu64"]  Hashing algorithm not the one we expected (sha-256), *NOT* cool\n", handle->handle_id);
265
                                        }
266
                                } else if(!strcasecmp(a->a_name, "setup")) {
267
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS setup (local):  %s\n", handle->handle_id, a->a_value);
268
                                        if(!strcasecmp(a->a_value, "actpass") || !strcasecmp(a->a_value, "passive"))
269
                                                stream->dtls_role = JANUS_DTLS_ROLE_CLIENT;
270
                                        else if(!strcasecmp(a->a_value, "active"))
271
                                                stream->dtls_role = JANUS_DTLS_ROLE_SERVER;
272
                                        /* TODO Handle holdconn... */
273
                                } else if(!strcasecmp(a->a_name, "ice-ufrag")) {
274
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE ufrag (local):   %s\n", handle->handle_id, a->a_value);
275
                                        if(ruser)
276
                                                g_free(ruser);        /* FIXME We're overwriting the global one, if any */
277
                                        ruser = g_strdup(a->a_value);
278
                                } else if(!strcasecmp(a->a_name, "ice-pwd")) {
279
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE pwd (local):     %s\n", handle->handle_id, a->a_value);
280
                                        if(rpass)
281
                                                g_free(rpass);        /* FIXME We're overwriting the global one, if any */
282
                                        rpass = g_strdup(a->a_value);
283
                                }
284
                        }
285
                        a = a->a_next;
286
                }
287
                if(!ruser || !rpass || !rfingerprint || !rhashing) {
288
                        /* Missing mandatory information, failure... */
289
                        if(ruser)
290
                                g_free(ruser);
291
                        ruser = NULL;
292
                        if(rpass)
293
                                g_free(rpass);
294
                        rpass = NULL;
295
                        if(rhashing)
296
                                g_free(rhashing);
297
                        rhashing = NULL;
298
                        if(rfingerprint)
299
                                g_free(rfingerprint);
300
                        rfingerprint = NULL;
301
                        return -2;
302
                }
303
                /* FIXME We're replacing the fingerprint info, assuming it's going to be the same for all media */
304
                if(stream->remote_hashing != NULL)
305
                        g_free(stream->remote_hashing);
306
                stream->remote_hashing = g_strdup(rhashing);
307
                if(stream->remote_fingerprint != NULL)
308
                        g_free(stream->remote_fingerprint);
309
                stream->remote_fingerprint = g_strdup(rfingerprint);
310
                /* Store the ICE username and password for this stream */
311
                if(stream->ruser != NULL)
312
                        g_free(stream->ruser);
313
                stream->ruser = g_strdup(ruser);
314
                if(stream->rpass != NULL)
315
                        g_free(stream->rpass);
316
                stream->rpass = g_strdup(rpass);
317
                /* Now look for candidates and other info */
318
                a = m->m_attributes;
319
                while(a) {
320
                        if(a->a_name) {
321
                                if(!strcasecmp(a->a_name, "candidate")) {
322
                                        if(m->m_type == sdp_media_video && handle->audio_id > 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) {
323
                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is a video candidate but we're bundling, ignoring...\n", handle->handle_id);
324
#ifdef HAVE_SCTP
325
                                        } else if(m->m_type == sdp_media_application && (handle->audio_id > 0 || handle->video_id > 0) && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE)) {
326
                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] This is a SCTP candidate but we're bundling, ignoring...\n", handle->handle_id);
327
#endif
328
                                        } else {
329
                                                int res = janus_sdp_parse_candidate(stream, (const char *)a->a_value, 0);
330
                                                if(res != 0) {
331
                                                        JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate... (%d)\n", handle->handle_id, res);
332
                                                }
333
                                        }
334
                                }
335
                                if(!strcasecmp(a->a_name, "ssrc")) {
336
                                        int res = janus_sdp_parse_ssrc(stream, (const char *)a->a_value, m->m_type == sdp_media_video);
337
                                        if(res != 0) {
338
                                                JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC attribute... (%d)\n", handle->handle_id, res);
339
                                        }
340
                                }
341
#ifdef HAVE_SCTP
342
                                if(!strcasecmp(a->a_name, "sctpmap")) {
343
                                        /* TODO Parse sctpmap line to get the UDP-port value and the number of channels */
344
                                        JANUS_LOG(LOG_VERB, "Got a sctpmap attribute: %s\n", a->a_value);
345
                                }
346
#endif
347
                        }
348
                        a = a->a_next;
349
                }
350
                m = m->m_next;
351
        }
352
        if(ruser)
353
                g_free(ruser);
354
        ruser = NULL;
355
        if(rpass)
356
                g_free(rpass);
357
        rpass = NULL;
358
        if(rhashing)
359
                g_free(rhashing);
360
        rhashing = NULL;
361
        if(rfingerprint)
362
                g_free(rfingerprint);
363
        rfingerprint = NULL;
364
        return 0;        /* FIXME Handle errors better */
365
}
366

    
367
int janus_sdp_parse_candidate(janus_ice_stream *stream, const char *candidate, int trickle) {
368
        if(stream == NULL || candidate == NULL)
369
                return -1;
370
        janus_ice_handle *handle = stream->handle;
371
        if(handle == NULL)
372
                return -2;
373
        janus_ice_component *component = NULL;
374
        if(strstr(candidate, "end-of-candidates")) {
375
                /* FIXME Should we do something with this? */
376
                JANUS_LOG(LOG_VERB, "[%"SCNu64"] end-of-candidates received\n", handle->handle_id);
377
                return 0;
378
        }
379
        if(strstr(candidate, "candidate:") == candidate) {
380
                /* Skipping the 'candidate:' prefix Firefox puts in trickle candidates */
381
                candidate += strlen("candidate:");
382
        }
383
        char rfoundation[32], rtransport[4], rip[40], rtype[6], rrelip[40];
384
        guint32 rcomponent, rpriority, rport, rrelport;
385
        int res = sscanf(candidate, "%31s %30u %3s %30u %39s %30u typ %5s %*s %39s %*s %30u",
386
                rfoundation, &rcomponent, rtransport, &rpriority,
387
                        rip, &rport, rtype, rrelip, &rrelport);
388
        if(res < 7) {
389
                /* Failed to parse this address, can it be IPv6? */
390
                if(!janus_ice_is_ipv6_enabled()) {
391
                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Received IPv6 candidate, but IPv6 support is disabled...\n", handle->handle_id);
392
                        return res;
393
                }
394
        }
395
        if(res >= 7) {
396
                /* Add remote candidate */
397
                component = g_hash_table_lookup(stream->components, GUINT_TO_POINTER(rcomponent));
398
                if(component == NULL) {
399
                        if(rcomponent == 2 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX)) {
400
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]   -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, stream->stream_id);
401
                        } else {
402
                                JANUS_LOG(LOG_ERR, "[%"SCNu64"]   -- No such component %d in stream %d?\n", handle->handle_id, rcomponent, stream->stream_id);
403
                        }
404
                } else {
405
                        if(rcomponent == 2 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX)) {
406
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]   -- Skipping component %d in stream %d (rtcp-muxing)\n", handle->handle_id, rcomponent, stream->stream_id);
407
                                return 0;
408
                        }
409
                        //~ if(trickle) {
410
                                //~ if(component->dtls != NULL) {
411
                                        //~ /* This component is already ready, ignore this further candidate */
412
                                        //~ JANUS_LOG(LOG_VERB, "[%"SCNu64"]   -- Ignoring this candidate, the component is already ready\n", handle->handle_id);
413
                                        //~ return 0;
414
                                //~ }
415
                        //~ }
416
                        component->component_id = rcomponent;
417
                        component->stream_id = stream->stream_id;
418
                        NiceCandidate *c = NULL;
419
                        if(!strcasecmp(rtype, "host")) {
420
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Adding remote candidate component:%d stream:%d type:host %s:%d\n",
421
                                        handle->handle_id, rcomponent, stream->stream_id, rip, rport);
422
                                /* Unless this is libnice >= 0.1.8, we only support UDP... */
423
                                if(!strcasecmp(rtransport, "udp")) {
424
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_HOST);
425
#ifdef HAVE_LIBNICE_TCP
426
                                } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) {
427
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_HOST);
428
#endif
429
                                } else {
430
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]    Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport);
431
                                }
432
                        } else if(!strcasecmp(rtype, "srflx")) {
433
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Adding remote candidate component:%d stream:%d type:srflx %s:%d --> %s:%d \n",
434
                                        handle->handle_id, rcomponent, stream->stream_id,  rrelip, rrelport, rip, rport);
435
                                /* Unless this is libnice >= 0.1.8, we only support UDP... */
436
                                if(!strcasecmp(rtransport, "udp")) {
437
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE);
438
#ifdef HAVE_LIBNICE_TCP
439
                                } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) {
440
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE);
441
#endif
442
                                } else {
443
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]    Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport);
444
                                }
445
                        } else if(!strcasecmp(rtype, "prflx")) {
446
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Adding remote candidate component:%d stream:%d type:prflx %s:%d --> %s:%d\n",
447
                                        handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport);
448
                                /* Unless this is libnice >= 0.1.8, we only support UDP... */
449
                                if(!strcasecmp(rtransport, "udp")) {
450
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
451
#ifdef HAVE_LIBNICE_TCP
452
                                } else if(!strcasecmp(rtransport, "tcp") && janus_ice_is_ice_tcp_enabled()) {
453
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
454
#endif
455
                                } else {
456
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]    Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport);
457
                                }
458
                        } else if(!strcasecmp(rtype, "relay")) {
459
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Adding remote candidate component:%d stream:%d type:relay %s:%d --> %s:%d\n",
460
                                        handle->handle_id, rcomponent, stream->stream_id, rrelip, rrelport, rip, rport);
461
                                /* We only support UDP/TCP/TLS... */
462
                                if(strcasecmp(rtransport, "udp") && strcasecmp(rtransport, "tcp") && strcasecmp(rtransport, "tls")) {
463
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]    Skipping unsupported transport '%s' for media\n", handle->handle_id, rtransport);
464
                                } else {
465
                                        c = nice_candidate_new(NICE_CANDIDATE_TYPE_RELAYED);
466
                                }
467
                        } else {
468
                                /* FIXME What now? */
469
                                JANUS_LOG(LOG_ERR, "[%"SCNu64"]  Unknown remote candidate type:%s for component:%d stream:%d!\n",
470
                                        handle->handle_id, rtype, rcomponent, stream->stream_id);
471
                        }
472
                        if(c != NULL) {
473
                                c->component_id = rcomponent;
474
                                c->stream_id = stream->stream_id;
475
#ifndef HAVE_LIBNICE_TCP
476
                                c->transport = NICE_CANDIDATE_TRANSPORT_UDP;
477
#else
478
                                if(!strcasecmp(rtransport, "udp")) {
479
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Transport: UDP\n", handle->handle_id);
480
                                        c->transport = NICE_CANDIDATE_TRANSPORT_UDP;
481
                                } else {
482
                                        /* Check the type (https://tools.ietf.org/html/rfc6544#section-4.5) */
483
                                        const char *type = NULL;
484
                                        int ctype = 0;
485
                                        if(strstr(candidate, "tcptype active")) {
486
                                                type = "active";
487
                                                ctype = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
488
                                        } else if(strstr(candidate, "tcptype passive")) {
489
                                                type = "passive";
490
                                                ctype = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
491
                                        } else if(strstr(candidate, "tcptype so")) {
492
                                                type = "so";
493
                                                ctype = NICE_CANDIDATE_TRANSPORT_TCP_SO;
494
                                        } else {
495
                                                /* TODO: We should actually stop here... */
496
                                                JANUS_LOG(LOG_ERR, "[%"SCNu64"] Missing tcptype info for the TCP candidate!\n", handle->handle_id);
497
                                        }
498
                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"]  Transport: TCP (%s)\n", handle->handle_id, type);
499
                                        c->transport = ctype;
500
                                }
501
#endif
502
                                strncpy(c->foundation, rfoundation, NICE_CANDIDATE_MAX_FOUNDATION);
503
                                c->priority = rpriority;
504
                                nice_address_set_from_string(&c->addr, rip);
505
                                nice_address_set_port(&c->addr, rport);
506
                                c->username = g_strdup(stream->ruser);
507
                                c->password = g_strdup(stream->rpass);
508
                                if(c->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE || c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
509
                                        nice_address_set_from_string(&c->base_addr, rrelip);
510
                                        nice_address_set_port(&c->base_addr, rrelport);
511
                                } else if(c->type == NICE_CANDIDATE_TYPE_RELAYED) {
512
                                        /* FIXME Do we really need the base address for TURN? */
513
                                        nice_address_set_from_string(&c->base_addr, rrelip);
514
                                        nice_address_set_port(&c->base_addr, rrelport);
515
                                }
516
                                component->candidates = g_slist_append(component->candidates, c);
517
                                JANUS_LOG(LOG_HUGE, "[%"SCNu64"]    Candidate added to the list! (%u elements for %d/%d)\n", handle->handle_id,
518
                                        g_slist_length(component->candidates), stream->stream_id, component->component_id);
519
                                /* Save for the summary, in case we need it */
520
                                component->remote_candidates = g_slist_append(component->remote_candidates, g_strdup(candidate));
521
                                if(trickle) {
522
                                        if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START)) {
523
                                                /* This is a trickle candidate and ICE has started, we should process it right away */
524
                                                if(!component->process_started) {
525
                                                        /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */
526
                                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] ICE already started for this component, setting candidates we have up to now\n", handle->handle_id);
527
                                                        janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id);
528
                                                } else {
529
                                                        GSList *candidates = NULL;
530
                                                        candidates = g_slist_append(candidates, c);
531
                                                        if(nice_agent_set_remote_candidates(handle->agent, stream->stream_id, component->component_id, candidates) < 1) {
532
                                                                JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to add trickle candidate :-(\n", handle->handle_id);
533
                                                        } else {
534
                                                                JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Trickle candidate added!\n", handle->handle_id);
535
                                                        }
536
                                                        g_slist_free(candidates);
537
                                                }
538
                                        } else {
539
                                                /* ICE hasn't started yet: to make sure we're not stuck, also check if we stopped processing the SDP */
540
                                                if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PROCESSING_OFFER)) {
541
                                                        janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_START);
542
                                                        /* This is a trickle candidate and ICE has started, we should process it right away */
543
                                                        if(!component->process_started) {
544
                                                                /* Actually, ICE has JUST started for this component, take care of the candidates we've added so far */
545
                                                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] SDP processed but ICE not started yet for this component, setting candidates we have up to now\n", handle->handle_id);
546
                                                                janus_ice_setup_remote_candidates(handle, component->stream_id, component->component_id);
547
                                                        } else {
548
                                                                GSList *candidates = NULL;
549
                                                                candidates = g_slist_append(candidates, c);
550
                                                                if(nice_agent_set_remote_candidates(handle->agent, stream->stream_id, component->component_id, candidates) < 1) {
551
                                                                        JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to add trickle candidate :-(\n", handle->handle_id);
552
                                                                } else {
553
                                                                        JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Trickle candidate added!\n", handle->handle_id);
554
                                                                }
555
                                                                g_slist_free(candidates);
556
                                                        }
557
                                                } else {
558
                                                        /* Still processing the offer/answer: queue the trickle candidate for now, we'll process it later */
559
                                                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Queueing trickle candidate, status is not START yet\n", handle->handle_id);
560
                                                }
561
                                        }
562
                                }
563
                        }
564
                }
565
        } else {
566
                JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate (res=%d)...\n", handle->handle_id, res);
567
                return res;
568
        }
569
        return 0;
570
}
571

    
572
int janus_sdp_parse_ssrc(janus_ice_stream *stream, const char *ssrc_attr, int video) {
573
        if(stream == NULL || ssrc_attr == NULL)
574
                return -1;
575
        janus_ice_handle *handle = stream->handle;
576
        if(handle == NULL)
577
                return -2;
578
        guint64 ssrc = g_ascii_strtoull(ssrc_attr, NULL, 0);
579
        if(ssrc == 0 || ssrc > G_MAXUINT32)
580
                return -3;
581
        if(video) {
582
                if(stream->video_ssrc_peer == 0) {
583
                        stream->video_ssrc_peer = ssrc;
584
                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %u\n", handle->handle_id, stream->video_ssrc_peer);
585
                } else if(stream->video_ssrc_peer != ssrc) {
586
                        /* FIXME We assume the second SSRC we get is the one Chrome associates with retransmissions, e.g.
587
                         *         a=ssrc-group:FID 586466331 2053167359 (SSRC SSRC-rtx)
588
                         * SSRC group FID: https://tools.ietf.org/html/rfc3388#section-7 */
589
                        stream->video_ssrc_peer_rtx = ssrc;
590
                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (rtx): %u\n", handle->handle_id, stream->video_ssrc_peer_rtx);
591
                }
592
        } else {
593
                if(stream->audio_ssrc_peer == 0) {
594
                        stream->audio_ssrc_peer = ssrc;
595
                        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer audio SSRC: %u\n", handle->handle_id, stream->audio_ssrc_peer);
596
                }
597
        }
598
        return 0;
599
}
600

    
601
char *janus_sdp_anonymize(const char *sdp) {
602
        if(sdp == NULL)
603
                return NULL;
604
        sdp_session_t *anon = NULL;
605
        sdp_parser_t *parser = sdp_parse(home, sdp, strlen(sdp), 0);
606
        if(!(anon = sdp_session(parser))) {
607
                JANUS_LOG(LOG_ERR, "Error parsing/merging SDP: %s\n", sdp_parsing_error(parser));
608
                sdp_parser_free(parser);
609
                return NULL;
610
        }
611
        /* c= */
612
        if(anon->sdp_connection && anon->sdp_connection->c_address) {
613
                anon->sdp_connection->c_address = "1.1.1.1";
614
        }
615
        /* a= */
616
        if(anon->sdp_attributes) {
617
                /* These are attributes we handle ourselves, the plugins don't need them */
618
                while(sdp_attribute_find(anon->sdp_attributes, "ice-ufrag"))
619
                        sdp_attribute_remove(&anon->sdp_attributes, "ice-ufrag");
620
                while(sdp_attribute_find(anon->sdp_attributes, "ice-pwd"))
621
                        sdp_attribute_remove(&anon->sdp_attributes, "ice-pwd");
622
                while(sdp_attribute_find(anon->sdp_attributes, "ice-options"))
623
                        sdp_attribute_remove(&anon->sdp_attributes, "ice-options");
624
                while(sdp_attribute_find(anon->sdp_attributes, "fingerprint"))
625
                        sdp_attribute_remove(&anon->sdp_attributes, "fingerprint");
626
                while(sdp_attribute_find(anon->sdp_attributes, "group"))
627
                        sdp_attribute_remove(&anon->sdp_attributes, "group");
628
                while(sdp_attribute_find(anon->sdp_attributes, "msid-semantic"))
629
                        sdp_attribute_remove(&anon->sdp_attributes, "msid-semantic");
630
                while(sdp_attribute_find(anon->sdp_attributes, "rtcp-rsize"))
631
                        sdp_attribute_remove(&anon->sdp_attributes, "rtcp-rsize");
632
        }
633
                /* m= */
634
        if(anon->sdp_media) {
635
                int audio = 0, video = 0, data = 0;
636
                sdp_media_t *m = anon->sdp_media;
637
                while(m) {
638
                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
639
                                audio++;
640
                                m->m_port = audio == 1 ? 1 : 0;
641
                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
642
                                video++;
643
                                m->m_port = video == 1 ? 1 : 0;
644
                        } else if(m->m_type == sdp_media_application) {
645
                                if(m->m_proto_name != NULL && !strcasecmp(m->m_proto_name, "DTLS/SCTP") && m->m_port != 0) {
646
                                        data++;
647
                                        m->m_port = data == 1 ? 1 : 0;
648
                                } else {
649
                                        m->m_port = 0;
650
                                }
651
                        } else {
652
                                m->m_port = 0;
653
                        }
654
                                /* c= */
655
                        if(m->m_connections) {
656
                                sdp_connection_t *c = m->m_connections;
657
                                while(c) {
658
                                        if(c->c_address) {
659
                                                c->c_address = "1.1.1.1";
660
                                        }
661
                                        c = c->c_next;
662
                                }
663
                        }
664
                                /* a= */
665
                        if(m->m_attributes) {
666
                                /* These are attributes we handle ourselves, the plugins don't need them */
667
                                while(sdp_attribute_find(m->m_attributes, "ice-ufrag"))
668
                                        sdp_attribute_remove(&m->m_attributes, "ice-ufrag");
669
                                while(sdp_attribute_find(m->m_attributes, "ice-pwd"))
670
                                        sdp_attribute_remove(&m->m_attributes, "ice-pwd");
671
                                while(sdp_attribute_find(m->m_attributes, "ice-options"))
672
                                        sdp_attribute_remove(&m->m_attributes, "ice-options");
673
                                while(sdp_attribute_find(m->m_attributes, "crypto"))
674
                                        sdp_attribute_remove(&m->m_attributes, "crypto");
675
                                while(sdp_attribute_find(m->m_attributes, "fingerprint"))
676
                                        sdp_attribute_remove(&m->m_attributes, "fingerprint");
677
                                while(sdp_attribute_find(m->m_attributes, "setup"))
678
                                        sdp_attribute_remove(&m->m_attributes, "setup");
679
                                while(sdp_attribute_find(m->m_attributes, "connection"))
680
                                        sdp_attribute_remove(&m->m_attributes, "connection");
681
                                while(sdp_attribute_find(m->m_attributes, "group"))
682
                                        sdp_attribute_remove(&m->m_attributes, "group");
683
                                while(sdp_attribute_find(m->m_attributes, "mid"))
684
                                        sdp_attribute_remove(&m->m_attributes, "mid");
685
                                while(sdp_attribute_find(m->m_attributes, "msid"))
686
                                        sdp_attribute_remove(&m->m_attributes, "msid");
687
                                while(sdp_attribute_find(m->m_attributes, "msid-semantic"))
688
                                        sdp_attribute_remove(&m->m_attributes, "msid-semantic");
689
                                while(sdp_attribute_find(m->m_attributes, "rtcp"))
690
                                        sdp_attribute_remove(&m->m_attributes, "rtcp");
691
                                while(sdp_attribute_find(m->m_attributes, "rtcp-mux"))
692
                                        sdp_attribute_remove(&m->m_attributes, "rtcp-mux");
693
                                while(sdp_attribute_find(m->m_attributes, "rtcp-rsize"))
694
                                        sdp_attribute_remove(&m->m_attributes, "rtcp-rsize");
695
                                while(sdp_attribute_find(m->m_attributes, "candidate"))
696
                                        sdp_attribute_remove(&m->m_attributes, "candidate");
697
                                while(sdp_attribute_find(m->m_attributes, "ssrc"))
698
                                        sdp_attribute_remove(&m->m_attributes, "ssrc");
699
                                while(sdp_attribute_find(m->m_attributes, "ssrc-group"))
700
                                        sdp_attribute_remove(&m->m_attributes, "ssrc-group");
701
                                while(sdp_attribute_find(m->m_attributes, "extmap"))        /* TODO Actually implement RTP extensions */
702
                                        sdp_attribute_remove(&m->m_attributes, "extmap");
703
                                while(sdp_attribute_find(m->m_attributes, "sctpmap"))
704
                                        sdp_attribute_remove(&m->m_attributes, "sctpmap");
705
                                /* Also remove attributes/formats we know we don't support now */
706
                                GList *ptypes = NULL;
707
                                sdp_attribute_t *a = m->m_attributes;
708
                                while(a) {
709
                                        if(a->a_value && (strstr(a->a_value, "red/90000") || strstr(a->a_value, "ulpfec/90000") || strstr(a->a_value, "rtx/90000"))) {
710
                                                int ptype = atoi(a->a_value);
711
                                                ptypes = g_list_append(ptypes, GINT_TO_POINTER(ptype));
712
                                                JANUS_LOG(LOG_VERB, "Will remove payload type %d\n", ptype);
713
                                        }
714
                                        a = a->a_next;
715
                                }
716
                                if(ptypes) {
717
                                        GList *p = ptypes;
718
                                        while(p) {
719
                                                int ptype = GPOINTER_TO_INT(p->data);
720
                                                if(m->m_format) {
721
                                                        sdp_list_t *fmt = m->m_format, *old = NULL;
722
                                                        while(fmt) {
723
                                                                int fmt_pt = atoi(fmt->l_text);
724
                                                                if(fmt_pt == ptype) {
725
                                                                        if(!old) {
726
                                                                                m->m_format = fmt->l_next;
727
                                                                                fmt = m->m_format;
728
                                                                                continue;
729
                                                                        } else {
730
                                                                                old->l_next = fmt->l_next;
731
                                                                                fmt = fmt->l_next;
732
                                                                                continue;
733
                                                                        }
734
                                                                }
735
                                                                old = fmt;
736
                                                                fmt = fmt->l_next;
737
                                                        }
738
                                                }
739
                                                a = m->m_attributes;
740
                                                sdp_attribute_t *old = NULL;
741
                                                while(a) {
742
                                                        int a_pt = a->a_value ? atoi(a->a_value) : -1;
743
                                                        if(a_pt == ptype) {
744
                                                                if(!old) {
745
                                                                        m->m_attributes = a->a_next;
746
                                                                        a = m->m_attributes;
747
                                                                        continue;
748
                                                                } else {
749
                                                                        old->a_next = a->a_next;
750
                                                                        a = a->a_next;
751
                                                                        continue;
752
                                                                }
753
                                                        }
754
                                                        old = a;
755
                                                        a = a->a_next;
756
                                                }
757
                                                p = g_list_remove(p, p->data);
758
                                        }
759
                                }
760
                        }
761
                        if(m->m_type != sdp_media_application && m->m_mode == sdp_sendrecv) {
762
                                /* FIXME sendrecv hack: sofia-sdp doesn't print sendrecv, but we want it to */
763
                                sdp_attribute_append(&m->m_attributes, &fakedir);
764
                        }
765
                        m = m->m_next;
766
                }
767
        }
768
        char buf[JANUS_BUFSIZE];
769
        sdp_printer_t *printer = sdp_print(home, anon, buf, JANUS_BUFSIZE, 0);
770
        if(sdp_message(printer)) {
771
                int retval = sdp_message_size(printer);
772
                sdp_printer_free(printer);
773
                sdp_parser_free(parser);
774
                /* FIXME Take care of the sendrecv hack, if needed */
775
                char *replace = strstr(buf, "a=jfmod:sr");
776
                while(replace != NULL) {
777
                        memcpy(replace, "a=sendrecv", strlen("a=sendrecv"));
778
                        replace++;
779
                        replace = strstr(replace, "a=jfmod:sr");
780
                }
781
                JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
782
                JANUS_LOG(LOG_VERB, "  >> Anonymized (%zu --> %d bytes)\n", strlen(sdp), retval);
783
                JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
784
                JANUS_LOG(LOG_VERB, "%s\n", buf);
785
                return g_strdup(buf);
786
        } else {
787
                JANUS_LOG(LOG_ERR, "Error anonymizing SDP: %s\n", sdp_printing_error(printer));
788
                sdp_printer_free(printer);
789
                sdp_parser_free(parser);
790
                return NULL;
791
        }
792
}
793

    
794
char *janus_sdp_merge(janus_ice_handle *handle, const char *origsdp) {
795
        if(handle == NULL || origsdp == NULL)
796
                return NULL;
797
        sdp_session_t *anon = NULL;
798
        sdp_parser_t *parser = sdp_parse(home, origsdp, strlen(origsdp), 0);
799
        if(!(anon = sdp_session(parser))) {
800
                JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error parsing/merging SDP: %s\n", handle->handle_id, sdp_parsing_error(parser));
801
                sdp_parser_free(parser);
802
                return NULL;
803
        }
804
        /* Prepare SDP to merge */
805
        gchar buffer[512];
806
        memset(buffer, 0, 512);
807
        char *sdp = (char*)g_malloc0(JANUS_BUFSIZE);
808
        if(sdp == NULL) {
809
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
810
                sdp_parser_free(parser);
811
                return NULL;
812
        }
813
        sdp[0] = '\0';
814
        char *rtp_profile = handle->rtp_profile ? handle->rtp_profile : (char *)"RTP/SAVPF";
815
        /* FIXME Any Plan B to take into account? */
816
        int planb = strstr(origsdp, "a=planb:") ? 1 : 0;
817
        if(planb) {
818
                janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B);
819
        } else {
820
                janus_flags_clear(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_PLAN_B);
821
        }
822
        /* Version v= */
823
        g_strlcat(sdp,
824
                "v=0\r\n", JANUS_BUFSIZE);
825
        /* Origin o= */
826
        if(anon->sdp_origin) {
827
                g_snprintf(buffer, 512,
828
                        "o=%s %"SCNu64" %"SCNu64" IN IP4 %s\r\n",
829
                                anon->sdp_origin->o_username ? anon->sdp_origin->o_username : "-",
830
                                anon->sdp_origin->o_id, anon->sdp_origin->o_version,
831
                                janus_get_public_ip());
832
                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
833
        } else {
834
                gint64 sessid = janus_get_real_time();
835
                gint64 version = sessid;        /* FIXME This needs to be increased when it changes, so time should be ok */
836
                g_snprintf(buffer, 512,
837
                        "o=%s %"SCNi64" %"SCNi64" IN IP4 %s\r\n",
838
                                "-", sessid, version, janus_get_public_ip());
839
                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
840
        }
841
        /* Session name s= */
842
        if(anon->sdp_subject && strlen(anon->sdp_subject) > 0) {
843
                g_snprintf(buffer, 512, "s=%s\r\n", anon->sdp_subject);
844
        } else {
845
                g_snprintf(buffer, 512, "s=%s\r\n", "Meetecho Janus");
846
        }
847
        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
848
        /* Timing t= */
849
        g_snprintf(buffer, 512,
850
                "t=%lu %lu\r\n", anon->sdp_time ? anon->sdp_time->t_start : 0, anon->sdp_time ? anon->sdp_time->t_stop : 0);
851
        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
852
        /* ICE Full or Lite? */
853
        if(janus_ice_is_ice_lite_enabled()) {
854
                /* Janus is acting in ICE Lite mode, advertize this */
855
                g_strlcat(sdp, "a=ice-lite\r\n", JANUS_BUFSIZE);
856
        }
857
        /* bundle: add new global attribute */
858
        int audio = (strstr(origsdp, "m=audio") != NULL);
859
        int video = (strstr(origsdp, "m=video") != NULL);
860
#ifdef HAVE_SCTP
861
        int data = (strstr(origsdp, "DTLS/SCTP") && !strstr(origsdp, " 0 DTLS/SCTP"));
862
#else
863
        int data = 0;
864
#endif
865
        g_strlcat(sdp, "a=group:BUNDLE", JANUS_BUFSIZE);
866
        if(audio) {
867
                g_snprintf(buffer, 512,
868
                        " %s", handle->audio_mid ? handle->audio_mid : "audio");
869
                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
870
        }
871
        if(video) {
872
                g_snprintf(buffer, 512,
873
                        " %s", handle->video_mid ? handle->video_mid : "video");
874
                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
875
        }
876
        if(data) {
877
                g_snprintf(buffer, 512,
878
                        " %s", handle->data_mid ? handle->data_mid : "data");
879
                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
880
        }
881
        g_strlcat(sdp, "\r\n", JANUS_BUFSIZE);
882
        /* msid-semantic: add new global attribute */
883
        g_strlcat(sdp,
884
                "a=msid-semantic: WMS janus\r\n",
885
                JANUS_BUFSIZE);
886
        char wms[JANUS_BUFSIZE];
887
        memset(wms, 0, JANUS_BUFSIZE);
888
        g_strlcat(wms, "WMS", JANUS_BUFSIZE);
889
        /* Copy other global attributes, if any */
890
        if(anon->sdp_attributes) {
891
                sdp_attribute_t *a = anon->sdp_attributes;
892
                while(a) {
893
                        if(a->a_value == NULL) {
894
                                g_snprintf(buffer, 512,
895
                                        "a=%s\r\n", a->a_name);
896
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
897
                        } else {
898
                                g_snprintf(buffer, 512,
899
                                        "a=%s:%s\r\n", a->a_name, a->a_value);
900
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
901
                        }
902
                        a = a->a_next;
903
                }
904
        }
905
        gboolean ipv6 = strstr(janus_get_public_ip(), ":") != NULL;
906
        /* Media lines now */
907
        if(anon->sdp_media) {
908
                int audio = 0, video = 0;
909
#ifdef HAVE_SCTP
910
                int data = 0;
911
#endif
912
                sdp_media_t *m = anon->sdp_media;
913
                janus_ice_stream *stream = NULL;
914
                while(m) {
915
                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
916
                                audio++;
917
                                if(audio > 1 || !handle->audio_id) {
918
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (we have %d audio lines, and the id is %d)\n", handle->handle_id, audio, handle->audio_id);
919
                                        g_snprintf(buffer, 512,
920
                                                "m=audio 0 %s 0\r\n", rtp_profile);
921
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
922
                                        /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
923
                                        g_snprintf(buffer, 512,
924
                                                "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
925
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
926
                                        g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
927
                                        m = m->m_next;
928
                                        continue;
929
                                }
930
                                /* Audio */
931
                                stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(handle->audio_id));
932
                                if(stream == NULL) {
933
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping audio line (invalid stream %d)\n", handle->handle_id, handle->audio_id);
934
                                        g_snprintf(buffer, 512,
935
                                                "m=audio 0 %s 0\r\n", rtp_profile);
936
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
937
                                        /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
938
                                        g_snprintf(buffer, 512,
939
                                                "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
940
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
941
                                        g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
942
                                        m = m->m_next;
943
                                        continue;
944
                                }
945
                                g_snprintf(buffer, 512,
946
                                        "m=audio 1 %s", rtp_profile);
947
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
948
                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
949
                                video++;
950
                                gint id = handle->video_id;
951
                                if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE))
952
                                        id = handle->audio_id > 0 ? handle->audio_id : handle->video_id;
953
                                if(video > 1 || !id) {
954
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (we have %d video lines, and the id is %d)\n", handle->handle_id, video,
955
                                                janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE) ? handle->audio_id : handle->video_id);
956
                                        g_snprintf(buffer, 512,
957
                                                "m=video 0 %s 0\r\n", rtp_profile);
958
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
959
                                        /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
960
                                        g_snprintf(buffer, 512,
961
                                                "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
962
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
963
                                        g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
964
                                        m = m->m_next;
965
                                        continue;
966
                                }
967
                                /* Video */
968
                                stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
969
                                if(stream == NULL) {
970
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping video line (invalid stream %d)\n", handle->handle_id, id);
971
                                        g_snprintf(buffer, 512,
972
                                                "m=video 0 %s 0\r\n", rtp_profile);
973
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
974
                                        /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
975
                                        g_snprintf(buffer, 512,
976
                                                "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
977
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
978
                                        g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
979
                                        m = m->m_next;
980
                                        continue;
981
                                }
982
                                g_snprintf(buffer, 512,
983
                                        "m=video 1 %s", rtp_profile);
984
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
985
#ifdef HAVE_SCTP
986
                        } else if(m->m_type == sdp_media_application) {
987
                                /* Is this SCTP for DataChannels? */
988
                                if(m->m_port > 0 && m->m_proto_name != NULL && !strcasecmp(m->m_proto_name, "DTLS/SCTP") && m->m_port > 0) {
989
                                        /* Yep */
990
                                        data++;
991
                                        gint id = handle->data_id;
992
                                        if(id == 0 && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_BUNDLE))
993
                                                id = handle->audio_id > 0 ? handle->audio_id : handle->video_id;
994
                                        if(data > 1 || !id) {
995
                                                JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (we have %d SCTP lines, and the id is %d)\n", handle->handle_id, data, id);
996
                                                g_snprintf(buffer, 512,
997
                                                        "m=%s 0 %s 0\r\n",
998
                                                        m->m_type_name, m->m_proto_name);
999
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1000
                                                /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
1001
                                                g_snprintf(buffer, 512,
1002
                                                        "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
1003
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1004
                                                m = m->m_next;
1005
                                                continue;
1006
                                        }
1007
                                        /* SCTP */
1008
                                        stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(id));
1009
                                        if(stream == NULL) {
1010
                                                JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping SCTP line (invalid stream %d)\n", handle->handle_id, id);
1011
                                                g_snprintf(buffer, 512,
1012
                                                        "m=%s 0 %s 0\r\n",
1013
                                                        m->m_type_name, m->m_proto_name);
1014
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1015
                                                /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
1016
                                                g_snprintf(buffer, 512,
1017
                                                        "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
1018
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1019
                                                m = m->m_next;
1020
                                                continue;
1021
                                        }
1022
                                        g_strlcat(sdp, "m=application 1 DTLS/SCTP", JANUS_BUFSIZE);
1023
                                } else {
1024
                                        JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping unsupported application media line...\n", handle->handle_id);
1025
                                        g_snprintf(buffer, 512,
1026
                                                "m=%s 0 %s 0\r\n",
1027
                                                m->m_type_name, m->m_proto_name);
1028
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1029
                                        m = m->m_next;
1030
                                        continue;
1031
                                }
1032
#endif
1033
                        } else {
1034
                                JANUS_LOG(LOG_WARN, "[%"SCNu64"] Skipping disabled/unsupported media line...\n", handle->handle_id);
1035
                                g_snprintf(buffer, 512,
1036
                                        "m=%s 0 %s 0\r\n",
1037
                                        m->m_type_name, m->m_type == sdp_media_application ? m->m_proto_name : rtp_profile);
1038
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1039
                                /* FIXME Adding a c-line anyway because otherwise Firefox complains? ("c= connection line not specified for every media level, validation failed") */
1040
                                g_snprintf(buffer, 512,
1041
                                        "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
1042
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1043
                                if(m->m_type == sdp_media_audio || m->m_type == sdp_media_video)
1044
                                        g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
1045
                                m = m->m_next;
1046
                                continue;
1047
                        }
1048
                        /* Add formats now */
1049
                        if(!m->m_rtpmaps) {
1050
                                JANUS_LOG(LOG_VERB, "[%"SCNu64"] No RTP maps?? trying formats...\n", handle->handle_id);
1051
                                if(!m->m_format) {
1052
                                        JANUS_LOG(LOG_ERR, "[%"SCNu64"] No formats either?? this sucks!\n", handle->handle_id);
1053
                                        g_strlcat(sdp, " 0", JANUS_BUFSIZE);        /* FIXME Won't work apparently */
1054
                                } else {
1055
                                        sdp_list_t *fmt = m->m_format;
1056
                                        while(fmt) {
1057
                                                g_snprintf(buffer, 512, " %s", fmt->l_text);
1058
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1059
                                                guint16 pt = atoi(fmt->l_text);
1060
                                                if(m->m_type == sdp_media_audio) {
1061
                                                        stream->audio_payload_types = g_list_append(stream->audio_payload_types, GUINT_TO_POINTER(pt));
1062
                                                } else if(m->m_type == sdp_media_video) {
1063
                                                        stream->video_payload_types = g_list_append(stream->video_payload_types, GUINT_TO_POINTER(pt));
1064
                                                }
1065
                                                fmt = fmt->l_next;
1066
                                        }
1067
                                }
1068
                        } else {
1069
                                sdp_rtpmap_t *r = m->m_rtpmaps;
1070
                                while(r) {
1071
                                        g_snprintf(buffer, 512, " %d", r->rm_pt);
1072
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1073
                                        guint16 pt = r->rm_pt;
1074
                                        if(m->m_type == sdp_media_audio) {
1075
                                                stream->audio_payload_types = g_list_append(stream->audio_payload_types, GUINT_TO_POINTER(pt));
1076
                                        } else if(m->m_type == sdp_media_video) {
1077
                                                stream->video_payload_types = g_list_append(stream->video_payload_types, GUINT_TO_POINTER(pt));
1078
                                        }
1079
                                        r = r->rm_next;
1080
                                }
1081
                        }
1082
                        g_strlcat(sdp, "\r\n", JANUS_BUFSIZE);
1083
                        /* Media connection c= */
1084
                        g_snprintf(buffer, 512,
1085
                                "c=IN %s %s\r\n", ipv6 ? "IP6" : "IP4", janus_get_public_ip());
1086
                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1087
                        /* Any bandwidth? */
1088
                        if(m->m_bandwidths) {
1089
                                g_snprintf(buffer, 512,
1090
                                        "b=%s:%lu\r\n",        /* FIXME Are we doing this correctly? */
1091
                                                m->m_bandwidths->b_modifier_name ? m->m_bandwidths->b_modifier_name : "AS",
1092
                                                m->m_bandwidths->b_value);
1093
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1094
                        }
1095
                        /* a=mid:(audio|video|data) */
1096
                        switch(m->m_type) {
1097
                                case sdp_media_audio:
1098
                                        g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->audio_mid ? handle->audio_mid : "audio");
1099
                                        break;
1100
                                case sdp_media_video:
1101
                                        g_snprintf(buffer, 512, "a=mid:%s\r\n", handle->video_mid ? handle->video_mid : "video");
1102
                                        break;
1103
#ifdef HAVE_SCTP
1104
                                case sdp_media_application:
1105
                                        /* FIXME sctpmap and webrtc-datachannel should be dynamic */
1106
                                        g_snprintf(buffer, 512, "a=mid:%s\r\na=sctpmap:5000 webrtc-datachannel 16\r\n",
1107
                                                handle->data_mid ? handle->data_mid : "data");
1108
                                        break;
1109
#endif
1110
                                default:
1111
                                        break;
1112
                        }
1113
                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1114
                        if(m->m_type != sdp_media_application) {
1115
                                /* What is the direction? */
1116
                                switch(m->m_mode) {
1117
                                        case sdp_sendonly:
1118
                                                g_strlcat(sdp, "a=sendonly\r\n", JANUS_BUFSIZE);
1119
                                                break;
1120
                                        case sdp_recvonly:
1121
                                                g_strlcat(sdp, "a=recvonly\r\n", JANUS_BUFSIZE);
1122
                                                break;
1123
                                        case sdp_inactive:
1124
                                                g_strlcat(sdp, "a=inactive\r\n", JANUS_BUFSIZE);
1125
                                                break;
1126
                                        case sdp_sendrecv:
1127
                                        default:
1128
                                                g_strlcat(sdp, "a=sendrecv\r\n", JANUS_BUFSIZE);
1129
                                                break;
1130
                                }
1131
                                /* rtcp-mux */
1132
                                g_snprintf(buffer, 512, "a=rtcp-mux\r\n");
1133
                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1134
                                /* RTP maps */
1135
                                if(m->m_rtpmaps) {
1136
                                        sdp_rtpmap_t *rm = NULL;
1137
                                        for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
1138
                                                g_snprintf(buffer, 512, "a=rtpmap:%u %s/%lu%s%s\r\n",
1139
                                                        rm->rm_pt, rm->rm_encoding, rm->rm_rate,
1140
                                                        rm->rm_params ? "/" : "", 
1141
                                                        rm->rm_params ? rm->rm_params : "");
1142
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1143
                                        }
1144
                                        for(rm = m->m_rtpmaps; rm; rm = rm->rm_next) {
1145
                                                if(rm->rm_fmtp) {
1146
                                                        g_snprintf(buffer, 512, "a=fmtp:%u %s\r\n", rm->rm_pt, rm->rm_fmtp);
1147
                                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1148
                                                }
1149
                                        }
1150
                                }
1151
                        }
1152
                        /* ICE ufrag and pwd, DTLS fingerprint setup and connection a= */
1153
                        gchar *ufrag = NULL;
1154
                        gchar *password = NULL;
1155
                        nice_agent_get_local_credentials(handle->agent, stream->stream_id, &ufrag, &password);
1156
                        memset(buffer, 0, 100);
1157
                        g_snprintf(buffer, 512,
1158
                                "a=ice-ufrag:%s\r\n"
1159
                                "a=ice-pwd:%s\r\n"
1160
                                "a=ice-options:trickle\r\n"
1161
                                "a=fingerprint:sha-256 %s\r\n"
1162
                                "a=setup:%s\r\n"
1163
                                "a=connection:new\r\n",
1164
                                        ufrag, password,
1165
                                        janus_dtls_get_local_fingerprint(),
1166
                                        janus_get_dtls_srtp_role(stream->dtls_role));
1167
                        if(ufrag != NULL)
1168
                                g_free(ufrag);
1169
                        ufrag = NULL;
1170
                        if(password != NULL)
1171
                                g_free(password);
1172
                        password = NULL;
1173
                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1174
                        /* Copy existing media attributes, if any */
1175
                        if(m->m_attributes) {
1176
                                sdp_attribute_t *a = m->m_attributes;
1177
                                while(a) {
1178
                                        if(!strcmp(a->a_name, "planb")) {
1179
                                                /* Skip the fake planb attribute, it's for internal use only */
1180
                                                a = a->a_next;
1181
                                                continue;
1182
                                        }
1183
                                        if(a->a_value == NULL) {
1184
                                                g_snprintf(buffer, 512,
1185
                                                        "a=%s\r\n", a->a_name);
1186
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1187
                                        } else {
1188
                                                /* Make sure this attribute is related to a payload type we know of */
1189
                                                if(m->m_type == sdp_media_application) {
1190
                                                        g_snprintf(buffer, 512,
1191
                                                                "a=%s:%s\r\n", a->a_name, a->a_value);
1192
                                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1193
                                                } else {
1194
                                                        int pt = atoi(a->a_value);
1195
                                                        GList *pts = m->m_type == sdp_media_audio ? stream->audio_payload_types : stream->video_payload_types;
1196
                                                        while(pts) {
1197
                                                                guint16 media_pt = GPOINTER_TO_UINT(pts->data);
1198
                                                                if(pt == media_pt) {
1199
                                                                        g_snprintf(buffer, 512,
1200
                                                                                "a=%s:%s\r\n", a->a_name, a->a_value);
1201
                                                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1202
                                                                        break;
1203
                                                                }
1204
                                                                pts = pts->next;
1205
                                                        }
1206
                                                }
1207
                                        }
1208
                                        a = a->a_next;
1209
                                }
1210
                        }
1211
                        /* Add last attributes, rtcp and ssrc (msid) */
1212
                        if(!planb) {
1213
                                /* Single SSRC */
1214
                                if(m->m_type == sdp_media_audio && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) {
1215
                                        g_snprintf(buffer, 512,
1216
                                                "a=ssrc:%"SCNu32" cname:janusaudio\r\n"
1217
                                                "a=ssrc:%"SCNu32" msid:janus janusa0\r\n"
1218
                                                "a=ssrc:%"SCNu32" mslabel:janus\r\n"
1219
                                                "a=ssrc:%"SCNu32" label:janusa0\r\n",
1220
                                                        stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc, stream->audio_ssrc);
1221
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1222
                                } else if(m->m_type == sdp_media_video && m->m_mode != sdp_inactive && m->m_mode != sdp_recvonly) {
1223
                                        g_snprintf(buffer, 512,
1224
                                                "a=ssrc:%"SCNu32" cname:janusvideo\r\n"
1225
                                                "a=ssrc:%"SCNu32" msid:janus janusv0\r\n"
1226
                                                "a=ssrc:%"SCNu32" mslabel:janus\r\n"
1227
                                                "a=ssrc:%"SCNu32" label:janusv0\r\n",
1228
                                                        stream->video_ssrc, stream->video_ssrc, stream->video_ssrc, stream->video_ssrc);
1229
                                        g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1230
                                }
1231
                        } else {
1232
                                /* Multiple SSRCs */
1233
                                char mslabel[255];
1234
                                memset(mslabel, 0, 255);
1235
                                if(m->m_attributes) {
1236
                                        char id[256];
1237
                                        uint32_t ssrc = 0;
1238
                                        sdp_attribute_t *a = m->m_attributes;
1239
                                        while(a) {
1240
                                                if(a->a_name == NULL || a->a_value == NULL || strcmp(a->a_name, "planb")) {
1241
                                                        a = a->a_next;
1242
                                                        continue;
1243
                                                }
1244
                                                if(sscanf(a->a_value, "%255s %"SCNu32"", id, &ssrc) != 2) {
1245
                                                        JANUS_LOG(LOG_ERR, "Error parsing 'planb' attribute, skipping it...\n");
1246
                                                        a = a->a_next;
1247
                                                        continue;
1248
                                                }
1249
                                                JANUS_LOG(LOG_VERB, "Parsing 'planb' attribute: %s\n", a->a_value);
1250
                                                /* Add proper SSRC attributes */
1251
                                                if(m->m_type == sdp_media_audio) {
1252
                                                        g_snprintf(buffer, 512,
1253
                                                                "a=ssrc:%"SCNu32" cname:%saudio\r\n"
1254
                                                                "a=ssrc:%"SCNu32" msid:%s %sa0\r\n"
1255
                                                                "a=ssrc:%"SCNu32" mslabel:%s\r\n"
1256
                                                                "a=ssrc:%"SCNu32" label:%sa0\r\n",
1257
                                                                        ssrc, id, ssrc, id, id, ssrc, id, ssrc, id);
1258
                                                } else if(m->m_type == sdp_media_video) {
1259
                                                        g_snprintf(buffer, 512,
1260
                                                                "a=ssrc:%"SCNu32" cname:%svideo\r\n"
1261
                                                                "a=ssrc:%"SCNu32" msid:%s %sv0\r\n"
1262
                                                                "a=ssrc:%"SCNu32" mslabel:%s\r\n"
1263
                                                                "a=ssrc:%"SCNu32" label:%sv0\r\n",
1264
                                                                        ssrc, id, ssrc, id, id, ssrc, id, ssrc, id);
1265
                                                }
1266
                                                g_strlcat(sdp, buffer, JANUS_BUFSIZE);
1267
                                                /* Add to msid-semantic, if needed */
1268
                                                if(!strstr(wms, id)) {
1269
                                                        g_snprintf(mslabel, 255, " %s", id);
1270
                                                        g_strlcat(wms, mslabel, JANUS_BUFSIZE);
1271
                                                }
1272
                                                /* Go on */
1273
                                                a = a->a_next;
1274
                                        }
1275
                                }
1276
                        }
1277
                        /* And now the candidates */
1278
                        janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 1);
1279
                        if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX) &&
1280
                                        m->m_type != sdp_media_application)
1281
                                janus_ice_candidates_to_sdp(handle, sdp, stream->stream_id, 2);
1282
                        /* Next */
1283
                        m = m->m_next;
1284
                }
1285
        }
1286

    
1287
        /* Do we need to update the msid-semantic attribute? */
1288
        if(planb) {
1289
                sdp = janus_string_replace(sdp, "WMS janus", wms);
1290
        }
1291
        
1292
        sdp_parser_free(parser);
1293

    
1294
        JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
1295
        JANUS_LOG(LOG_VERB, "  >> Merged (%zu --> %zu bytes)\n", strlen(origsdp), strlen(sdp));
1296
        JANUS_LOG(LOG_VERB, " -------------------------------------------\n");
1297
        JANUS_LOG(LOG_VERB, "%s\n", sdp);
1298

    
1299
        return sdp;
1300
}