1 /*! \file    sdp-utils.c
2  * \author   Lorenzo Miniero <lorenzo@meetecho.com>
3  * \copyright GNU General Public License v3
4  * \brief    SDP utilities
5  * \details  Implementation of an internal SDP representation. Allows
6  * to parse SDP strings to an internal janus_sdp object, the manipulation
7  * of such object by playing with its properties, and a serialization
8  * to an SDP string that can be passed around. Since they don't have any
9  * core dependencies, these utilities can be used by plugins as well.
10  *
11  * \ingroup core
12  * \ref core
13  */
14 
15 #include <string.h>
16 
17 #include "sdp-utils.h"
18 #include "rtp.h"
19 #include "utils.h"
20 #include "debug.h"
21 
22 #define JANUS_BUFSIZE	8192
23 
24 /* Preferred codecs when negotiating audio/video, and number of supported codecs */
25 const char *janus_preferred_audio_codecs[] = {
26 	"opus", "multiopus", "pcmu", "pcma", "g722", "isac16", "isac32"
27 };
28 uint janus_audio_codecs = sizeof(janus_preferred_audio_codecs)/sizeof(*janus_preferred_audio_codecs);
29 const char *janus_preferred_video_codecs[] = {
30 	"vp8", "vp9", "h264", "av1", "h265"
31 };
32 uint janus_video_codecs = sizeof(janus_preferred_video_codecs)/sizeof(*janus_preferred_video_codecs);
33 
34 /* Reference counters management */
janus_sdp_destroy(janus_sdp * sdp)35 void janus_sdp_destroy(janus_sdp *sdp) {
36 	if(!sdp || !g_atomic_int_compare_and_exchange(&sdp->destroyed, 0, 1))
37 		return;
38 	janus_refcount_decrease(&sdp->ref);
39 }
40 
janus_sdp_mline_destroy(janus_sdp_mline * m)41 void janus_sdp_mline_destroy(janus_sdp_mline *m) {
42 	if(!m || !g_atomic_int_compare_and_exchange(&m->destroyed, 0, 1))
43 		return;
44 	janus_refcount_decrease(&m->ref);
45 }
46 
janus_sdp_attribute_destroy(janus_sdp_attribute * a)47 void janus_sdp_attribute_destroy(janus_sdp_attribute *a) {
48 	if(!a || !g_atomic_int_compare_and_exchange(&a->destroyed, 0, 1))
49 		return;
50 	janus_refcount_decrease(&a->ref);
51 }
52 
53 /* Internal frees */
janus_sdp_free(const janus_refcount * sdp_ref)54 static void janus_sdp_free(const janus_refcount *sdp_ref) {
55 	janus_sdp *sdp = janus_refcount_containerof(sdp_ref, janus_sdp, ref);
56 	/* This SDP instance can be destroyed, free all the resources */
57 	g_free(sdp->o_name);
58 	g_free(sdp->o_addr);
59 	g_free(sdp->s_name);
60 	g_free(sdp->c_addr);
61 	GList *temp = sdp->attributes;
62 	while(temp) {
63 		janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data;
64 		janus_sdp_attribute_destroy(a);
65 		temp = temp->next;
66 	}
67 	g_list_free(sdp->attributes);
68 	sdp->attributes = NULL;
69 	temp = sdp->m_lines;
70 	while(temp) {
71 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
72 		janus_sdp_mline_destroy(m);
73 		temp = temp->next;
74 	}
75 	g_list_free(sdp->m_lines);
76 	sdp->m_lines = NULL;
77 	g_free(sdp);
78 }
79 
janus_sdp_mline_free(const janus_refcount * mline_ref)80 static void janus_sdp_mline_free(const janus_refcount *mline_ref) {
81 	janus_sdp_mline *mline = janus_refcount_containerof(mline_ref, janus_sdp_mline, ref);
82 	/* This SDP m-line instance can be destroyed, free all the resources */
83 	g_free(mline->type_str);
84 	g_free(mline->proto);
85 	g_free(mline->c_addr);
86 	g_free(mline->b_name);
87 	g_list_free_full(mline->fmts, (GDestroyNotify)g_free);
88 	mline->fmts = NULL;
89 	g_list_free(mline->ptypes);
90 	mline->ptypes = NULL;
91 	GList *temp = mline->attributes;
92 	while(temp) {
93 		janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data;
94 		janus_sdp_attribute_destroy(a);
95 		temp = temp->next;
96 	}
97 	g_list_free(mline->attributes);
98 	g_free(mline);
99 }
100 
janus_sdp_attribute_free(const janus_refcount * attr_ref)101 static void janus_sdp_attribute_free(const janus_refcount *attr_ref) {
102 	janus_sdp_attribute *attr = janus_refcount_containerof(attr_ref, janus_sdp_attribute, ref);
103 	/* This SDP attribute instance can be destroyed, free all the resources */
104 	g_free(attr->name);
105 	g_free(attr->value);
106 	g_free(attr);
107 }
108 
109 
110 /* SDP and m-lines/attributes code */
janus_sdp_mline_create(janus_sdp_mtype type,guint16 port,const char * proto,janus_sdp_mdirection direction)111 janus_sdp_mline *janus_sdp_mline_create(janus_sdp_mtype type, guint16 port, const char *proto, janus_sdp_mdirection direction) {
112 	janus_sdp_mline *m = g_malloc0(sizeof(janus_sdp_mline));
113 	g_atomic_int_set(&m->destroyed, 0);
114 	janus_refcount_init(&m->ref, janus_sdp_mline_free);
115 	m->type = type;
116 	const char *type_str = janus_sdp_mtype_str(type);
117 	if(type_str == NULL) {
118 		JANUS_LOG(LOG_WARN, "Unknown media type, type_str will have to be set manually\n");
119 	} else {
120 		m->type_str = g_strdup(type_str);
121 	}
122 	m->port = port;
123 	m->proto = proto ? g_strdup(proto) : NULL;
124 	m->direction = direction;
125 	return m;
126 }
127 
janus_sdp_mline_find(janus_sdp * sdp,janus_sdp_mtype type)128 janus_sdp_mline *janus_sdp_mline_find(janus_sdp *sdp, janus_sdp_mtype type) {
129 	if(sdp == NULL)
130 		return NULL;
131 	GList *ml = sdp->m_lines;
132 	while(ml) {
133 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
134 		if(m->type == type)
135 			return m;
136 		ml = ml->next;
137 	}
138 	return NULL;
139 }
140 
janus_sdp_mline_remove(janus_sdp * sdp,janus_sdp_mtype type)141 int janus_sdp_mline_remove(janus_sdp *sdp, janus_sdp_mtype type) {
142 	if(sdp == NULL)
143 		return -1;
144 	GList *ml = sdp->m_lines;
145 	while(ml) {
146 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
147 		if(m->type == type) {
148 			/* Found! */
149 			sdp->m_lines = g_list_remove(sdp->m_lines, m);
150 			janus_sdp_mline_destroy(m);
151 			return 0;
152 		}
153 		ml = ml->next;
154 	}
155 	/* If we got here, we couldn't the m-line */
156 	return -2;
157 }
158 
janus_sdp_attribute_create(const char * name,const char * value,...)159 janus_sdp_attribute *janus_sdp_attribute_create(const char *name, const char *value, ...) {
160 	if(!name)
161 		return NULL;
162 	janus_sdp_attribute *a = g_malloc(sizeof(janus_sdp_attribute));
163 	g_atomic_int_set(&a->destroyed, 0);
164 	janus_refcount_init(&a->ref, janus_sdp_attribute_free);
165 	a->name = g_strdup(name);
166 	a->direction = JANUS_SDP_DEFAULT;
167 	a->value = NULL;
168 	if(value) {
169 		char buffer[512];
170 		va_list ap;
171 		va_start(ap, value);
172 		g_vsnprintf(buffer, sizeof(buffer), value, ap);
173 		va_end(ap);
174 		a->value = g_strdup(buffer);
175 	}
176 	return a;
177 }
178 
janus_sdp_attribute_add_to_mline(janus_sdp_mline * mline,janus_sdp_attribute * attr)179 int janus_sdp_attribute_add_to_mline(janus_sdp_mline *mline, janus_sdp_attribute *attr) {
180 	if(!mline || !attr)
181 		return -1;
182 	mline->attributes = g_list_append(mline->attributes, attr);
183 	return 0;
184 }
185 
janus_sdp_parse_mtype(const char * type)186 janus_sdp_mtype janus_sdp_parse_mtype(const char *type) {
187 	if(type == NULL)
188 		return JANUS_SDP_OTHER;
189 	if(!strcasecmp(type, "audio"))
190 		return JANUS_SDP_AUDIO;
191 	if(!strcasecmp(type, "video"))
192 		return JANUS_SDP_VIDEO;
193 	if(!strcasecmp(type, "application"))
194 		return JANUS_SDP_APPLICATION;
195 	return JANUS_SDP_OTHER;
196 }
197 
janus_sdp_mtype_str(janus_sdp_mtype type)198 const char *janus_sdp_mtype_str(janus_sdp_mtype type) {
199 	switch(type) {
200 		case JANUS_SDP_AUDIO:
201 			return "audio";
202 		case JANUS_SDP_VIDEO:
203 			return "video";
204 		case JANUS_SDP_APPLICATION:
205 			return "application";
206 		case JANUS_SDP_OTHER:
207 		default:
208 			break;
209 	}
210 	return NULL;
211 }
212 
janus_sdp_parse_mdirection(const char * direction)213 janus_sdp_mdirection janus_sdp_parse_mdirection(const char *direction) {
214 	if(direction == NULL)
215 		return JANUS_SDP_INVALID;
216 	if(!strcasecmp(direction, "sendrecv"))
217 		return JANUS_SDP_SENDRECV;
218 	if(!strcasecmp(direction, "sendonly"))
219 		return JANUS_SDP_SENDONLY;
220 	if(!strcasecmp(direction, "recvonly"))
221 		return JANUS_SDP_RECVONLY;
222 	if(!strcasecmp(direction, "inactive"))
223 		return JANUS_SDP_INACTIVE;
224 	return JANUS_SDP_INVALID;
225 }
226 
janus_sdp_mdirection_str(janus_sdp_mdirection direction)227 const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction) {
228 	switch(direction) {
229 		case JANUS_SDP_DEFAULT:
230 		case JANUS_SDP_SENDRECV:
231 			return "sendrecv";
232 		case JANUS_SDP_SENDONLY:
233 			return "sendonly";
234 		case JANUS_SDP_RECVONLY:
235 			return "recvonly";
236 		case JANUS_SDP_INACTIVE:
237 			return "inactive";
238 		case JANUS_SDP_INVALID:
239 		default:
240 			break;
241 	}
242 	return NULL;
243 }
244 
janus_sdp_parse(const char * sdp,char * error,size_t errlen)245 janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) {
246 	if(!sdp)
247 		return NULL;
248 	if(strstr(sdp, "v=") != sdp) {
249 		if(error)
250 			g_snprintf(error, errlen, "Invalid SDP (doesn't start with v=)");
251 		return NULL;
252 	}
253 	janus_sdp *imported = g_malloc0(sizeof(janus_sdp));
254 	g_atomic_int_set(&imported->destroyed, 0);
255 	janus_refcount_init(&imported->ref, janus_sdp_free);
256 	imported->o_ipv4 = TRUE;
257 	imported->c_ipv4 = TRUE;
258 
259 	gboolean success = TRUE;
260 	janus_sdp_mline *mline = NULL;
261 
262 	gchar **parts = g_strsplit(sdp, "\n", -1);
263 	if(parts) {
264 		int index = 0;
265 		char *line = NULL, *cr = NULL;
266 		while(success && (line = parts[index]) != NULL) {
267 			cr = strchr(line, '\r');
268 			if(cr != NULL)
269 				*cr = '\0';
270 			if(*line == '\0') {
271 				if(cr != NULL)
272 					*cr = '\r';
273 				index++;
274 				continue;
275 			}
276 			if(strlen(line) < 3) {
277 				if(error)
278 					g_snprintf(error, errlen, "Invalid line (%zu bytes): %s", strlen(line), line);
279 				success = FALSE;
280 				break;
281 			}
282 			if(*(line+1) != '=') {
283 				if(error)
284 					g_snprintf(error, errlen, "Invalid line (2nd char is not '='): %s", line);
285 				success = FALSE;
286 				break;
287 			}
288 			char c = *line;
289 			if(mline == NULL) {
290 				/* Global stuff */
291 				switch(c) {
292 					case 'v': {
293 						if(sscanf(line, "v=%d", &imported->version) != 1) {
294 							if(error)
295 								g_snprintf(error, errlen, "Invalid v= line: %s", line);
296 							success = FALSE;
297 							break;
298 						}
299 						break;
300 					}
301 					case 'o': {
302 						if(imported->o_name || imported->o_addr) {
303 							if(error)
304 								g_snprintf(error, errlen, "Multiple o= lines: %s", line);
305 							success = FALSE;
306 							break;
307 						}
308 						char name[256], addrtype[6], addr[256];
309 						if(sscanf(line, "o=%255s %"SCNu64" %"SCNu64" IN %5s %255s",
310 								name, &imported->o_sessid, &imported->o_version, addrtype, addr) != 5) {
311 							if(error)
312 								g_snprintf(error, errlen, "Invalid o= line: %s", line);
313 							success = FALSE;
314 							break;
315 						}
316 						if(!strcasecmp(addrtype, "IP4"))
317 							imported->o_ipv4 = TRUE;
318 						else if(!strcasecmp(addrtype, "IP6"))
319 							imported->o_ipv4 = FALSE;
320 						else {
321 							if(error)
322 								g_snprintf(error, errlen, "Invalid o= line (unsupported protocol %s): %s", addrtype, line);
323 							success = FALSE;
324 							break;
325 						}
326 						imported->o_name = g_strdup(name);
327 						imported->o_addr = g_strdup(addr);
328 						break;
329 					}
330 					case 's': {
331 						if(imported->s_name) {
332 							if(error)
333 								g_snprintf(error, errlen, "Multiple s= lines: %s", line);
334 							success = FALSE;
335 							break;
336 						}
337 						imported->s_name = g_strdup(line+2);
338 						break;
339 					}
340 					case 't': {
341 						if(sscanf(line, "t=%"SCNu64" %"SCNu64, &imported->t_start, &imported->t_stop) != 2) {
342 							if(error)
343 								g_snprintf(error, errlen, "Invalid t= line: %s", line);
344 							success = FALSE;
345 							break;
346 						}
347 						break;
348 					}
349 					case 'c': {
350 						if(imported->c_addr) {
351 							if(error)
352 								g_snprintf(error, errlen, "Multiple global c= lines: %s", line);
353 							success = FALSE;
354 							break;
355 						}
356 						char addrtype[6], addr[256];
357 						if(sscanf(line, "c=IN %5s %255s", addrtype, addr) != 2) {
358 							if(error)
359 								g_snprintf(error, errlen, "Invalid c= line: %s", line);
360 							success = FALSE;
361 							break;
362 						}
363 						if(!strcasecmp(addrtype, "IP4"))
364 							imported->c_ipv4 = TRUE;
365 						else if(!strcasecmp(addrtype, "IP6"))
366 							imported->c_ipv4 = FALSE;
367 						else {
368 							if(error)
369 								g_snprintf(error, errlen, "Invalid c= line (unsupported protocol %s): %s", addrtype, line);
370 							success = FALSE;
371 							break;
372 						}
373 						imported->c_addr = g_strdup(addr);
374 						break;
375 					}
376 					case 'a': {
377 						janus_sdp_attribute *a = g_malloc0(sizeof(janus_sdp_attribute));
378 						janus_refcount_init(&a->ref, janus_sdp_attribute_free);
379 						line += 2;
380 						char *semicolon = strchr(line, ':');
381 						if(semicolon == NULL) {
382 							a->name = g_strdup(line);
383 							a->value = NULL;
384 						} else {
385 							if(*(semicolon+1) == '\0') {
386 								janus_sdp_attribute_destroy(a);
387 								if(error)
388 									g_snprintf(error, errlen, "Invalid a= line: %s", line);
389 								success = FALSE;
390 								break;
391 							}
392 							*semicolon = '\0';
393 							a->name = g_strdup(line);
394 							a->value = g_strdup(semicolon+1);
395 							a->direction = JANUS_SDP_DEFAULT;
396 							*semicolon = ':';
397 							if(strstr(line, "/sendonly"))
398 								a->direction = JANUS_SDP_SENDONLY;
399 							else if(strstr(line, "/recvonly"))
400 								a->direction = JANUS_SDP_RECVONLY;
401 							if(strstr(line, "/inactive"))
402 								a->direction = JANUS_SDP_INACTIVE;
403 						}
404 						imported->attributes = g_list_prepend(imported->attributes, a);
405 						break;
406 					}
407 					case 'm': {
408 						janus_sdp_mline *m = g_malloc0(sizeof(janus_sdp_mline));
409 						g_atomic_int_set(&m->destroyed, 0);
410 						janus_refcount_init(&m->ref, janus_sdp_mline_free);
411 						/* Start with media type, port and protocol */
412 						char type[32];
413 						char proto[64];
414 						if(strlen(line) > 200) {
415 							janus_sdp_mline_destroy(m);
416 							if(error)
417 								g_snprintf(error, errlen, "Invalid m= line (too long): %zu", strlen(line));
418 							success = FALSE;
419 							break;
420 						}
421 						if(sscanf(line, "m=%31s %"SCNu16" %63s %*s", type, &m->port, proto) != 3) {
422 							janus_sdp_mline_destroy(m);
423 							if(error)
424 								g_snprintf(error, errlen, "Invalid m= line: %s", line);
425 							success = FALSE;
426 							break;
427 						}
428 						m->type = janus_sdp_parse_mtype(type);
429 						if(m->type == JANUS_SDP_OTHER) {
430 							janus_sdp_mline_destroy(m);
431 							if(error)
432 								g_snprintf(error, errlen, "Invalid m= line: %s", line);
433 							success = FALSE;
434 							break;
435 						}
436 						m->type_str = g_strdup(type);
437 						m->proto = g_strdup(proto);
438 						m->direction = JANUS_SDP_SENDRECV;
439 						m->c_ipv4 = TRUE;
440 						/* Now let's check the payload types/formats */
441 						gchar **mline_parts = g_strsplit(line+2, " ", -1);
442 						if(!mline_parts && (m->port > 0 || m->type == JANUS_SDP_APPLICATION)) {
443 							janus_sdp_mline_destroy(m);
444 							if(error)
445 								g_snprintf(error, errlen, "Invalid m= line (no payload types/formats): %s", line);
446 							success = FALSE;
447 							break;
448 						} else {
449 							int mindex = 0;
450 							while(mline_parts[mindex]) {
451 								if(mindex < 3) {
452 									/* We've parsed these before */
453 									mindex++;
454 									continue;
455 								}
456 								/* Add string fmt */
457 								m->fmts = g_list_prepend(m->fmts, g_strdup(mline_parts[mindex]));
458 								/* Add numeric payload type */
459 								int ptype = atoi(mline_parts[mindex]);
460 								if(ptype < 0) {
461 									JANUS_LOG(LOG_ERR, "Invalid payload type (%s)\n", mline_parts[mindex]);
462 								} else {
463 									m->ptypes = g_list_prepend(m->ptypes, GINT_TO_POINTER(ptype));
464 								}
465 								mindex++;
466 							}
467 							g_strfreev(mline_parts);
468 							if(m->fmts == NULL || m->ptypes == NULL) {
469 								janus_sdp_mline_destroy(m);
470 								if(error)
471 									g_snprintf(error, errlen, "Invalid m= line (no payload types/formats): %s", line);
472 								success = FALSE;
473 								break;
474 							}
475 							m->fmts = g_list_reverse(m->fmts);
476 							m->ptypes = g_list_reverse(m->ptypes);
477 						}
478 						/* Append to the list of m-lines */
479 						imported->m_lines = g_list_prepend(imported->m_lines, m);
480 						/* From now on, we parse this m-line */
481 						mline = m;
482 						break;
483 					}
484 					default:
485 						JANUS_LOG(LOG_WARN, "Ignoring '%c' property\n", c);
486 						break;
487 				}
488 			} else {
489 				/* m-line stuff */
490 				switch(c) {
491 					case 'c': {
492 						if(mline->c_addr) {
493 							if(error)
494 								g_snprintf(error, errlen, "Multiple m-line c= lines: %s", line);
495 							success = FALSE;
496 							break;
497 						}
498 						char addrtype[6], addr[256];
499 						if(sscanf(line, "c=IN %5s %255s", addrtype, addr) != 2) {
500 							if(error)
501 								g_snprintf(error, errlen, "Invalid c= line: %s", line);
502 							success = FALSE;
503 							break;
504 						}
505 						if(!strcasecmp(addrtype, "IP4"))
506 							mline->c_ipv4 = TRUE;
507 						else if(!strcasecmp(addrtype, "IP6"))
508 							mline->c_ipv4 = FALSE;
509 						else {
510 							if(error)
511 								g_snprintf(error, errlen, "Invalid c= line (unsupported protocol %s): %s", addrtype, line);
512 							success = FALSE;
513 							break;
514 						}
515 						mline->c_addr = g_strdup(addr);
516 						break;
517 					}
518 					case 'b': {
519 						if(mline->b_name) {
520 							JANUS_LOG(LOG_WARN, "Ignoring extra m-line b= line: %s\n", line);
521 							if(cr != NULL)
522 								*cr = '\r';
523 							index++;
524 							continue;
525 						}
526 						line += 2;
527 						char *semicolon = strchr(line, ':');
528 						if(semicolon == NULL || (*(semicolon+1) == '\0')) {
529 							if(error)
530 								g_snprintf(error, errlen, "Invalid b= line: %s", line);
531 							success = FALSE;
532 							break;
533 						}
534 						*semicolon = '\0';
535 						if(strcmp(line, "AS") && strcmp(line, "TIAS")) {
536 							/* We only support b=AS and b=TIAS, skip */
537 							break;
538 						}
539 						mline->b_name = g_strdup(line);
540 						mline->b_value = atol(semicolon+1);
541 						*semicolon = ':';
542 						break;
543 					}
544 					case 'a': {
545 						janus_sdp_attribute *a = g_malloc0(sizeof(janus_sdp_attribute));
546 						janus_refcount_init(&a->ref, janus_sdp_attribute_free);
547 						line += 2;
548 						char *semicolon = strchr(line, ':');
549 						if(semicolon == NULL) {
550 							/* Is this a media direction attribute? */
551 							janus_sdp_mdirection direction = janus_sdp_parse_mdirection(line);
552 							if(direction != JANUS_SDP_INVALID) {
553 								janus_sdp_attribute_destroy(a);
554 								mline->direction = direction;
555 								break;
556 							}
557 							a->name = g_strdup(line);
558 							a->value = NULL;
559 						} else {
560 							if(*(semicolon+1) == '\0') {
561 								janus_sdp_attribute_destroy(a);
562 								if(error)
563 									g_snprintf(error, errlen, "Invalid a= line: %s", line);
564 								success = FALSE;
565 								break;
566 							}
567 							*semicolon = '\0';
568 							a->name = g_strdup(line);
569 							a->value = g_strdup(semicolon+1);
570 							a->direction = JANUS_SDP_DEFAULT;
571 							*semicolon = ':';
572 							if(strstr(line, "/sendonly"))
573 								a->direction = JANUS_SDP_SENDONLY;
574 							else if(strstr(line, "/recvonly"))
575 								a->direction = JANUS_SDP_RECVONLY;
576 							if(strstr(line, "/inactive"))
577 								a->direction = JANUS_SDP_INACTIVE;
578 						}
579 						mline->attributes = g_list_prepend(mline->attributes, a);
580 						break;
581 					}
582 					case 'm': {
583 						/* Current m-line ended, back to global parsing */
584 						if(mline && mline->attributes)
585 							mline->attributes = g_list_reverse(mline->attributes);
586 						mline = NULL;
587 						continue;
588 					}
589 					default:
590 						JANUS_LOG(LOG_WARN, "Ignoring '%c' property (m-line)\n", c);
591 						break;
592 				}
593 			}
594 			if(cr != NULL)
595 				*cr = '\r';
596 			index++;
597 		}
598 		if(cr != NULL)
599 			*cr = '\r';
600 		g_strfreev(parts);
601 	}
602 	/* FIXME Do a last check: is all the stuff that's supposed to be there available? */
603 	if(success && (imported->o_name == NULL || imported->o_addr == NULL || imported->s_name == NULL || imported->m_lines == NULL)) {
604 		success = FALSE;
605 		if(error)
606 			g_snprintf(error, errlen, "Missing mandatory lines (o=, s= or m=)");
607 	}
608 	/* If something wrong happened, free and return a failure */
609 	if(!success) {
610 		if(error)
611 			JANUS_LOG(LOG_ERR, "%s\n", error);
612 		janus_sdp_destroy(imported);
613 		imported = NULL;
614 	} else {
615 		/* Reverse lists for efficiency */
616 		if(mline && mline->attributes)
617 			mline->attributes = g_list_reverse(mline->attributes);
618 		if(imported->attributes)
619 			imported->attributes = g_list_reverse(imported->attributes);
620 		if(imported->m_lines)
621 			imported->m_lines = g_list_reverse(imported->m_lines);
622 	}
623 	return imported;
624 }
625 
janus_sdp_remove_payload_type(janus_sdp * sdp,int pt)626 int janus_sdp_remove_payload_type(janus_sdp *sdp, int pt) {
627 	if(!sdp || pt < 0)
628 		return -1;
629 	GList *ml = sdp->m_lines;
630 	while(ml) {
631 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
632 		/* Remove any reference from the m-line */
633 		m->ptypes = g_list_remove(m->ptypes, GINT_TO_POINTER(pt));
634 		/* Also remove all attributes that reference the same payload type */
635 		GList *ma = m->attributes;
636 		while(ma) {
637 			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
638 			if(a->value && atoi(a->value) == pt) {
639 				m->attributes = g_list_remove(m->attributes, a);
640 				ma = m->attributes;
641 				janus_sdp_attribute_destroy(a);
642 				continue;
643 			}
644 			ma = ma->next;
645 		}
646 		ml = ml->next;
647 	}
648 	return 0;
649 }
650 
janus_sdp_get_codec_pt(janus_sdp * sdp,const char * codec)651 int janus_sdp_get_codec_pt(janus_sdp *sdp, const char *codec) {
652 	return janus_sdp_get_codec_pt_full(sdp, codec, NULL);
653 }
654 
janus_sdp_get_codec_pt_full(janus_sdp * sdp,const char * codec,const char * profile)655 int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *profile) {
656 	if(sdp == NULL || codec == NULL)
657 		return -1;
658 	/* Check the format string (note that we only parse what browsers can negotiate) */
659 	gboolean video = FALSE, vp9 = FALSE, h264 = FALSE;
660 	const char *format = NULL, *format2 = NULL;
661 	if(!strcasecmp(codec, "opus")) {
662 		format = "opus/48000/2";
663 		format2 = "OPUS/48000/2";
664 	} else if(!strcasecmp(codec, "multiopus")) {
665 		/* FIXME We're hardcoding to 6 channels, for now */
666 		format = "multiopus/48000/6";
667 		format2 = "MULTIOPUS/48000/6";
668 	} else if(!strcasecmp(codec, "pcmu")) {
669 		/* We know the payload type is 0: we just need to make sure it's there */
670 		format = "pcmu/8000";
671 		format2 = "PCMU/8000";
672 	} else if(!strcasecmp(codec, "pcma")) {
673 		/* We know the payload type is 8: we just need to make sure it's there */
674 		format = "pcma/8000";
675 		format2 = "PCMA/8000";
676 	} else if(!strcasecmp(codec, "g722")) {
677 		/* We know the payload type is 9: we just need to make sure it's there */
678 		format = "g722/8000";
679 		format2 = "G722/8000";
680 	} else if(!strcasecmp(codec, "isac16")) {
681 		format = "isac/16000";
682 		format2 = "ISAC/16000";
683 	} else if(!strcasecmp(codec, "isac32")) {
684 		format = "isac/32000";
685 		format2 = "ISAC/32000";
686 	} else if(!strcasecmp(codec, "dtmf")) {
687 		format = "telephone-event/8000";
688 		format2 = "TELEPHONE-EVENT/8000";
689 	} else if(!strcasecmp(codec, "vp8")) {
690 		video = TRUE;
691 		format = "vp8/90000";
692 		format2 = "VP8/90000";
693 	} else if(!strcasecmp(codec, "vp9")) {
694 		video = TRUE;
695 		vp9 = TRUE;		/* We may need to filter on profiles */
696 		format = "vp9/90000";
697 		format2 = "VP9/90000";
698 	} else if(!strcasecmp(codec, "h264")) {
699 		video = TRUE;
700 		h264 = TRUE;	/* We may need to filter on profiles */
701 		format = "h264/90000";
702 		format2 = "H264/90000";
703 	} else if(!strcasecmp(codec, "av1")) {
704 		video = TRUE;
705 		format = "av1x/90000";
706 		format2 = "AV1X/90000";
707 	} else if(!strcasecmp(codec, "h265")) {
708 		video = TRUE;
709 		format = "h265/90000";
710 		format2 = "H265/90000";
711 	} else {
712 		JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
713 		return -1;
714 	}
715 	/* Check all m->lines */
716 	GList *ml = sdp->m_lines;
717 	while(ml) {
718 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
719 		if((!video && m->type != JANUS_SDP_AUDIO) || (video && m->type != JANUS_SDP_VIDEO)) {
720 			ml = ml->next;
721 			continue;
722 		}
723 		/* Look in all rtpmap attributes first */
724 		GList *ma = m->attributes;
725 		int pt = -1;
726 		GList *pts = NULL;
727 		while(ma) {
728 			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
729 			if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) {
730 				pt = atoi(a->value);
731 				if(pt < 0) {
732 					JANUS_LOG(LOG_ERR, "Invalid payload type (%s)\n", a->value);
733 				} else if(strstr(a->value, format) || strstr(a->value, format2)) {
734 					if(profile != NULL && (vp9 || h264)) {
735 						/* Let's keep track of this payload type */
736 						pts = g_list_append(pts, GINT_TO_POINTER(pt));
737 					} else {
738 						/* Payload type for codec found */
739 						return pt;
740 					}
741 				}
742 			}
743 			ma = ma->next;
744 		}
745 		if(profile != NULL) {
746 			/* Now look for the profile in the fmtp attributes */
747 			ma = m->attributes;
748 			while(ma) {
749 				janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
750 				if(profile != NULL && a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) {
751 					/* Does this match the payload types we're looking for? */
752 					pt = atoi(a->value);
753 					if(g_list_find(pts, GINT_TO_POINTER(pt)) == NULL) {
754 						/* Not what we're looking for */
755 						ma = ma->next;
756 						continue;
757 					}
758 					if(vp9) {
759 						char profile_id[20];
760 						g_snprintf(profile_id, sizeof(profile_id), "profile-id=%s", profile);
761 						if(strstr(a->value, profile_id) != NULL) {
762 							/* Found */
763 							JANUS_LOG(LOG_VERB, "VP9 profile %s found --> %d\n", profile, pt);
764 							return pt;
765 						}
766 					} else if(h264 && strstr(a->value, "packetization-mode=0") == NULL) {
767 						/* We only support packetization-mode=1, no matter the profile */
768 						char profile_level_id[30];
769 						char *profile_lower = g_ascii_strdown(profile, -1);
770 						g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_lower);
771 						g_free(profile_lower);
772 						if(strstr(a->value, profile_level_id) != NULL) {
773 							/* Found */
774 							JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt);
775 							return pt;
776 						}
777 						/* Not found, try converting the profile to upper case */
778 						char *profile_upper = g_ascii_strup(profile, -1);
779 						g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_upper);
780 						g_free(profile_upper);
781 						if(strstr(a->value, profile_level_id) != NULL) {
782 							/* Found */
783 							JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt);
784 							return pt;
785 						}
786 					}
787 				}
788 				ma = ma->next;
789 			}
790 		}
791 		if(pts != NULL)
792 			g_list_free(pts);
793 		ml = ml->next;
794 	}
795 	return -1;
796 }
797 
janus_sdp_get_codec_name(janus_sdp * sdp,int pt)798 const char *janus_sdp_get_codec_name(janus_sdp *sdp, int pt) {
799 	if(sdp == NULL || pt < 0)
800 		return NULL;
801 	if(pt == 0)
802 		return "pcmu";
803 	if(pt == 8)
804 		return "pcma";
805 	if(pt == 9)
806 		return "g722";
807 	GList *ml = sdp->m_lines;
808 	while(ml) {
809 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
810 		/* Look in all rtpmap attributes */
811 		GList *ma = m->attributes;
812 		while(ma) {
813 			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
814 			if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) {
815 				int a_pt = atoi(a->value);
816 				if(a_pt == pt) {
817 					/* Found! */
818 					if(strstr(a->value, "vp8") || strstr(a->value, "VP8"))
819 						return "vp8";
820 					if(strstr(a->value, "vp9") || strstr(a->value, "VP9"))
821 						return "vp9";
822 					if(strstr(a->value, "h264") || strstr(a->value, "H264"))
823 						return "h264";
824 					if(strstr(a->value, "av1") || strstr(a->value, "AV1"))
825 						return "av1";
826 					if(strstr(a->value, "h265") || strstr(a->value, "H265"))
827 						return "h265";
828 					if(strstr(a->value, "multiopus") || strstr(a->value, "MULTIOPUS"))
829 						return "multiopus";
830 					if(strstr(a->value, "opus") || strstr(a->value, "OPUS"))
831 						return "opus";
832 					if(strstr(a->value, "pcmu") || strstr(a->value, "PCMU"))
833 						return "pcmu";
834 					if(strstr(a->value, "pcma") || strstr(a->value, "PCMA"))
835 						return "pcma";
836 					if(strstr(a->value, "g722") || strstr(a->value, "G722"))
837 						return "g722";
838 					if(strstr(a->value, "isac/16") || strstr(a->value, "ISAC/16"))
839 						return "isac16";
840 					if(strstr(a->value, "isac/32") || strstr(a->value, "ISAC/32"))
841 						return "isac32";
842 					if(strstr(a->value, "telephone-event/8000") || strstr(a->value, "telephone-event/8000"))
843 						return "dtmf";
844 					JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", a->value);
845 					return NULL;
846 				}
847 			}
848 			ma = ma->next;
849 		}
850 		ml = ml->next;
851 	}
852 	return NULL;
853 }
854 
janus_sdp_get_codec_rtpmap(const char * codec)855 const char *janus_sdp_get_codec_rtpmap(const char *codec) {
856 	if(codec == NULL)
857 		return NULL;
858 	if(!strcasecmp(codec, "opus"))
859 		return "opus/48000/2";
860 	if(!strcasecmp(codec, "multiopus"))
861 		/* FIXME We're hardcoding to 6 channels, for now */
862 		return "multiopus/48000/6";
863 	if(!strcasecmp(codec, "pcmu"))
864 		return "PCMU/8000";
865 	if(!strcasecmp(codec, "pcma"))
866 		return "PCMA/8000";
867 	if(!strcasecmp(codec, "g722"))
868 		return "G722/8000";
869 	if(!strcasecmp(codec, "isac16"))
870 		return "ISAC/16000";
871 	if(!strcasecmp(codec, "isac32"))
872 		return "ISAC/32000";
873 	if(!strcasecmp(codec, "dtmf"))
874 		return "telephone-event/8000";
875 	if(!strcasecmp(codec, "vp8"))
876 		return "VP8/90000";
877 	if(!strcasecmp(codec, "vp9"))
878 		return "VP9/90000";
879 	if(!strcasecmp(codec, "h264"))
880 		return "H264/90000";
881 	if(!strcasecmp(codec, "av1"))
882 		return "AV1X/90000";
883 	if(!strcasecmp(codec, "h265"))
884 		return "H265/90000";
885 	JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
886 	return NULL;
887 }
888 
janus_sdp_get_fmtp(janus_sdp * sdp,int pt)889 const char *janus_sdp_get_fmtp(janus_sdp *sdp, int pt) {
890 	if(sdp == NULL || pt < 0)
891 		return NULL;
892 	GList *ml = sdp->m_lines;
893 	while(ml) {
894 		janus_sdp_mline *m = (janus_sdp_mline *)ml->data;
895 		/* Look in all rtpmap attributes */
896 		GList *ma = m->attributes;
897 		while(ma) {
898 			janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
899 			if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) {
900 				int a_pt = atoi(a->value);
901 				if(a_pt == pt) {
902 					/* Found! */
903 					char needle[10];
904 					g_snprintf(needle, sizeof(needle), "%d ", pt);
905 					if(strstr(a->value, needle) == a->value)
906 						return a->value + strlen(needle);
907 				}
908 			}
909 			ma = ma->next;
910 		}
911 		ml = ml->next;
912 	}
913 	return NULL;
914 }
915 
janus_sdp_write(janus_sdp * imported)916 char *janus_sdp_write(janus_sdp *imported) {
917 	if(!imported)
918 		return NULL;
919 	janus_refcount_increase(&imported->ref);
920 	char *sdp = g_malloc(1024), mline[JANUS_BUFSIZE], buffer[512];
921 	*sdp = '\0';
922 	size_t sdplen = 1024, mlen = sizeof(mline);
923 	/* v= */
924 	g_snprintf(buffer, sizeof(buffer), "v=%d\r\n", imported->version);
925 	janus_strlcat(sdp, buffer, sdplen);
926 	/* o= */
927 	g_snprintf(buffer, sizeof(buffer), "o=%s %"SCNu64" %"SCNu64" IN %s %s\r\n",
928 		imported->o_name, imported->o_sessid, imported->o_version,
929 		imported->o_ipv4 ? "IP4" : "IP6", imported->o_addr);
930 	janus_strlcat(sdp, buffer, sdplen);
931 	/* s= */
932 	g_snprintf(buffer, sizeof(buffer), "s=%s\r\n", imported->s_name);
933 	janus_strlcat(sdp, buffer, sdplen);
934 	/* t= */
935 	g_snprintf(buffer, sizeof(buffer), "t=%"SCNu64" %"SCNu64"\r\n", imported->t_start, imported->t_stop);
936 	janus_strlcat(sdp, buffer, sdplen);
937 	/* c= */
938 	if(imported->c_addr != NULL) {
939 		g_snprintf(buffer, sizeof(buffer), "c=IN %s %s\r\n",
940 			imported->c_ipv4 ? "IP4" : "IP6", imported->c_addr);
941 		janus_strlcat(sdp, buffer, sdplen);
942 	}
943 	/* a= */
944 	GList *temp = imported->attributes;
945 	while(temp) {
946 		janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data;
947 		if(a->value != NULL) {
948 			g_snprintf(buffer, sizeof(buffer), "a=%s:%s\r\n", a->name, a->value);
949 		} else {
950 			g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", a->name);
951 		}
952 		janus_strlcat(sdp, buffer, sdplen);
953 		temp = temp->next;
954 	}
955 	/* m= */
956 	temp = imported->m_lines;
957 	while(temp) {
958 		mline[0] = '\0';
959 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
960 		g_snprintf(buffer, sizeof(buffer), "m=%s %d %s", m->type_str, m->port, m->proto);
961 		janus_strlcat(mline, buffer, mlen);
962 		if(m->port == 0 && m->type != JANUS_SDP_APPLICATION) {
963 			/* Remove all payload types/formats if we're rejecting the media */
964 			g_list_free_full(m->fmts, (GDestroyNotify)g_free);
965 			m->fmts = NULL;
966 			g_list_free(m->ptypes);
967 			m->ptypes = NULL;
968 			m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(0));
969 			janus_strlcat(mline, " 0", mlen);
970 		} else {
971 			if(m->proto != NULL && strstr(m->proto, "RTP") != NULL) {
972 				/* RTP profile, use payload types */
973 				GList *ptypes = m->ptypes;
974 				while(ptypes) {
975 					g_snprintf(buffer, sizeof(buffer), " %d", GPOINTER_TO_INT(ptypes->data));
976 					janus_strlcat(mline, buffer, mlen);
977 					ptypes = ptypes->next;
978 				}
979 			} else {
980 				/* Something else, use formats */
981 				GList *fmts = m->fmts;
982 				while(fmts) {
983 					g_snprintf(buffer, sizeof(buffer), " %s", (char *)(fmts->data));
984 					janus_strlcat(mline, buffer, mlen);
985 					fmts = fmts->next;
986 				}
987 			}
988 		}
989 		janus_strlcat(mline, "\r\n", mlen);
990 		/* c= */
991 		if(m->c_addr != NULL) {
992 			g_snprintf(buffer, sizeof(buffer), "c=IN %s %s\r\n",
993 				m->c_ipv4 ? "IP4" : "IP6", m->c_addr);
994 			janus_strlcat(mline, buffer, mlen);
995 		}
996 		if(m->port > 0) {
997 			/* b= */
998 			if(m->b_name != NULL) {
999 				g_snprintf(buffer, sizeof(buffer), "b=%s:%"SCNu32"\r\n", m->b_name, m->b_value);
1000 				janus_strlcat(mline, buffer, mlen);
1001 			}
1002 		}
1003 		/* a= (note that we don't format the direction if it's JANUS_SDP_DEFAULT) */
1004 		const char *direction = m->direction != JANUS_SDP_DEFAULT ? janus_sdp_mdirection_str(m->direction) : NULL;
1005 		if(direction != NULL) {
1006 			g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", direction);
1007 			janus_strlcat(mline, buffer, mlen);
1008 		}
1009 		GList *temp2 = m->attributes;
1010 		while(temp2) {
1011 			janus_sdp_attribute *a = (janus_sdp_attribute *)temp2->data;
1012 			if(m->port == 0 && strcasecmp(a->name, "mid")) {
1013 				/* This media has been rejected or disabled: we only add the mid attribute, if available */
1014 				temp2 = temp2->next;
1015 				continue;
1016 			}
1017 			if(a->value != NULL) {
1018 				g_snprintf(buffer, sizeof(buffer), "a=%s:%s\r\n", a->name, a->value);
1019 			} else {
1020 				g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", a->name);
1021 			}
1022 			janus_strlcat(mline, buffer, mlen);
1023 			temp2 = temp2->next;
1024 		}
1025 		/* Append the generated m-line to the SDP */
1026 		size_t cur_sdplen = strlen(sdp);
1027 		size_t mlinelen = strlen(mline);
1028 		if(cur_sdplen + mlinelen + 1 > sdplen) {
1029 			/* Increase the SDP buffer first */
1030 			if(sdplen < (mlinelen+1))
1031 				sdplen = cur_sdplen + mlinelen + 1;
1032 			else
1033 				sdplen = sdplen*2;
1034 			sdp = g_realloc(sdp, sdplen);
1035 		}
1036 		janus_strlcat(sdp, mline, sdplen);
1037 		/* Move on */
1038 		temp = temp->next;
1039 	}
1040 	janus_refcount_decrease(&imported->ref);
1041 	return sdp;
1042 }
1043 
janus_sdp_find_preferred_codecs(janus_sdp * sdp,const char ** acodec,const char ** vcodec)1044 void janus_sdp_find_preferred_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec) {
1045 	if(sdp == NULL)
1046 		return;
1047 	janus_refcount_increase(&sdp->ref);
1048 	gboolean audio = FALSE, video = FALSE;
1049 	GList *temp = sdp->m_lines;
1050 	while(temp) {
1051 		/* Which media are available? */
1052 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
1053 		if(m->type == JANUS_SDP_AUDIO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
1054 			if(audio == FALSE) {
1055 				uint i=0;
1056 				for(i=0; i<janus_audio_codecs; i++) {
1057 					if(janus_sdp_get_codec_pt(sdp, janus_preferred_audio_codecs[i]) > 0) {
1058 						audio = TRUE;
1059 						if(acodec)
1060 							*acodec = janus_preferred_audio_codecs[i];
1061 						break;
1062 					}
1063 				}
1064 			}
1065 		} else if(m->type == JANUS_SDP_VIDEO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
1066 			if(video == FALSE) {
1067 				uint i=0;
1068 				for(i=0; i<janus_video_codecs; i++) {
1069 					if(janus_sdp_get_codec_pt(sdp, janus_preferred_video_codecs[i]) > 0) {
1070 						video = TRUE;
1071 						if(vcodec)
1072 							*vcodec = janus_preferred_video_codecs[i];
1073 						break;
1074 					}
1075 				}
1076 			}
1077 		}
1078 		if(audio && video)
1079 			break;
1080 		temp = temp->next;
1081 	}
1082 	janus_refcount_decrease(&sdp->ref);
1083 }
1084 
janus_sdp_find_first_codecs(janus_sdp * sdp,const char ** acodec,const char ** vcodec)1085 void janus_sdp_find_first_codecs(janus_sdp *sdp, const char **acodec, const char **vcodec) {
1086 	if(sdp == NULL)
1087 		return;
1088 	janus_refcount_increase(&sdp->ref);
1089 	gboolean audio = FALSE, video = FALSE;
1090 	GList *temp = sdp->m_lines;
1091 	while(temp) {
1092 		/* Which media are available? */
1093 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
1094 		if(m->type == JANUS_SDP_AUDIO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
1095 			if(audio == FALSE && m->ptypes) {
1096 				int pt = GPOINTER_TO_INT(m->ptypes->data);
1097 				const char *codec = janus_sdp_get_codec_name(sdp, pt);
1098 				codec = janus_sdp_match_preferred_codec(m->type, (char *)codec);
1099 				if(codec) {
1100 					audio = TRUE;
1101 					if(acodec)
1102 						*acodec = codec;
1103 				}
1104 			}
1105 		} else if(m->type == JANUS_SDP_VIDEO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
1106 			if(video == FALSE && m->ptypes) {
1107 				int pt = GPOINTER_TO_INT(m->ptypes->data);
1108 				const char *codec = janus_sdp_get_codec_name(sdp, pt);
1109 				codec = janus_sdp_match_preferred_codec(m->type, (char *)codec);
1110 				if(codec) {
1111 					video = TRUE;
1112 					if(vcodec)
1113 						*vcodec = codec;
1114 				}
1115 			}
1116 		}
1117 		if(audio && video)
1118 			break;
1119 		temp = temp->next;
1120 	}
1121 	janus_refcount_decrease(&sdp->ref);
1122 }
1123 
janus_sdp_match_preferred_codec(janus_sdp_mtype type,char * codec)1124 const char *janus_sdp_match_preferred_codec(janus_sdp_mtype type, char *codec) {
1125 	if(codec == NULL)
1126 		return NULL;
1127 	if(type != JANUS_SDP_AUDIO && type != JANUS_SDP_VIDEO)
1128 		return NULL;
1129 	gboolean video = (type == JANUS_SDP_VIDEO);
1130 	uint i=0;
1131 	for(i=0; i<(video ? janus_video_codecs : janus_audio_codecs); i++) {
1132 		if(!strcasecmp(codec, (video ? janus_preferred_video_codecs[i] : janus_preferred_audio_codecs[i]))) {
1133 			/* Found! */
1134 			return video ? janus_preferred_video_codecs[i] : janus_preferred_audio_codecs[i];
1135 		}
1136 	}
1137 	return NULL;
1138 }
1139 
janus_sdp_new(const char * name,const char * address)1140 janus_sdp *janus_sdp_new(const char *name, const char *address) {
1141 	janus_sdp *sdp = g_malloc(sizeof(janus_sdp));
1142 	g_atomic_int_set(&sdp->destroyed, 0);
1143 	janus_refcount_init(&sdp->ref, janus_sdp_free);
1144 	/* Fill in some predefined stuff */
1145 	sdp->version = 0;
1146 	sdp->o_name = g_strdup("-");
1147 	sdp->o_sessid = janus_get_real_time();
1148 	sdp->o_version = 1;
1149 	sdp->o_ipv4 = TRUE;
1150 	sdp->o_addr = g_strdup(address ? address : "127.0.0.1");
1151 	sdp->s_name = g_strdup(name ? name : "Janus session");
1152 	sdp->t_start = 0;
1153 	sdp->t_stop = 0;
1154 	sdp->c_ipv4 = TRUE;
1155 	sdp->c_addr = g_strdup(address ? address : "127.0.0.1");
1156 	sdp->attributes = NULL;
1157 	sdp->m_lines = NULL;
1158 	/* Done */
1159 	return sdp;
1160 }
1161 
janus_sdp_id_compare(gconstpointer a,gconstpointer b)1162 static int janus_sdp_id_compare(gconstpointer a, gconstpointer b) {
1163 	return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
1164 }
janus_sdp_generate_offer(const char * name,const char * address,...)1165 janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...) {
1166 	/* This method has a variable list of arguments, telling us what we should offer */
1167 	va_list args;
1168 	va_start(args, address);
1169 	/* Let's see what we should do with the media */
1170 	gboolean do_audio = TRUE, do_video = TRUE, do_data = TRUE,
1171 		audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = TRUE;
1172 	const char *audio_codec = NULL, *video_codec = NULL,
1173 		*vp9_profile = NULL, *h264_profile = NULL,
1174 		*audio_fmtp = NULL, *video_fmtp = NULL;
1175 	int audio_pt = 111, video_pt = 96;
1176 	janus_sdp_mdirection audio_dir = JANUS_SDP_SENDRECV, video_dir = JANUS_SDP_SENDRECV;
1177 	GHashTable *audio_extmaps = NULL, *audio_extids = NULL,
1178 		*video_extmaps = NULL, *video_extids = NULL;
1179 	int property = va_arg(args, int);
1180 	while(property != JANUS_SDP_OA_DONE) {
1181 		if(property == JANUS_SDP_OA_AUDIO) {
1182 			do_audio = va_arg(args, gboolean);
1183 		} else if(property == JANUS_SDP_OA_VIDEO) {
1184 			do_video = va_arg(args, gboolean);
1185 		} else if(property == JANUS_SDP_OA_DATA) {
1186 			do_data = va_arg(args, gboolean);
1187 		} else if(property == JANUS_SDP_OA_AUDIO_DIRECTION) {
1188 			audio_dir = va_arg(args, janus_sdp_mdirection);
1189 		} else if(property == JANUS_SDP_OA_VIDEO_DIRECTION) {
1190 			video_dir = va_arg(args, janus_sdp_mdirection);
1191 		} else if(property == JANUS_SDP_OA_AUDIO_CODEC) {
1192 			audio_codec = va_arg(args, char *);
1193 		} else if(property == JANUS_SDP_OA_VIDEO_CODEC) {
1194 			video_codec = va_arg(args, char *);
1195 		} else if(property == JANUS_SDP_OA_VP9_PROFILE) {
1196 			vp9_profile = va_arg(args, char *);
1197 		} else if(property == JANUS_SDP_OA_H264_PROFILE) {
1198 			h264_profile = va_arg(args, char *);
1199 		} else if(property == JANUS_SDP_OA_AUDIO_PT) {
1200 			audio_pt = va_arg(args, int);
1201 		} else if(property == JANUS_SDP_OA_VIDEO_PT) {
1202 			video_pt = va_arg(args, int);
1203 		} else if(property == JANUS_SDP_OA_AUDIO_DTMF) {
1204 			audio_dtmf = va_arg(args, gboolean);
1205 		} else if(property == JANUS_SDP_OA_AUDIO_FMTP) {
1206 			audio_fmtp = va_arg(args, char *);
1207 		} else if(property == JANUS_SDP_OA_VIDEO_FMTP) {
1208 			video_fmtp = va_arg(args, char *);
1209 		} else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) {
1210 			video_rtcpfb = va_arg(args, gboolean);
1211 		} else if(property == JANUS_SDP_OA_DATA_LEGACY) {
1212 			data_legacy = va_arg(args, gboolean);
1213 		} else if(property == JANUS_SDP_OA_AUDIO_EXTENSION || property == JANUS_SDP_OA_VIDEO_EXTENSION) {
1214 			char *extmap = va_arg(args, char *);
1215 			int id = va_arg(args, int);
1216 			if(extmap != NULL && id > 0 && id < 15) {
1217 				if(audio_extmaps == NULL)
1218 					audio_extmaps = g_hash_table_new(g_str_hash, g_str_equal);
1219 				if(audio_extids == NULL)
1220 					audio_extids = g_hash_table_new(NULL, NULL);
1221 				if(video_extmaps == NULL)
1222 					video_extmaps = g_hash_table_new(g_str_hash, g_str_equal);
1223 				if(video_extids == NULL)
1224 					video_extids = g_hash_table_new(NULL, NULL);
1225 				/* Make sure the extmap and ID have not been added already */
1226 				char *audio_extmap = g_hash_table_lookup(audio_extids, GINT_TO_POINTER(id));
1227 				char *video_extmap = g_hash_table_lookup(video_extids, GINT_TO_POINTER(id));
1228 				if((property == JANUS_SDP_OA_VIDEO_EXTENSION && audio_extmap != NULL && strcasecmp(audio_extmap, extmap)) ||
1229 						(property == JANUS_SDP_OA_AUDIO_EXTENSION && video_extmap != NULL && strcasecmp(video_extmap, extmap))) {
1230 					JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %d (already added: %s)\n",
1231 						id, audio_extmap ? audio_extmap : video_extmap);
1232 				} else {
1233 					if(property == JANUS_SDP_OA_AUDIO_EXTENSION) {
1234 						if(g_hash_table_lookup(audio_extmaps, extmap) != NULL) {
1235 							JANUS_LOG(LOG_WARN, "Ignoring duplicate audio extension %s (already added: %d)\n",
1236 								extmap, GPOINTER_TO_INT(g_hash_table_lookup(audio_extmaps, extmap)));
1237 						} else {
1238 							g_hash_table_insert(audio_extmaps, extmap, GINT_TO_POINTER(id));
1239 							g_hash_table_insert(audio_extids, GINT_TO_POINTER(id), extmap);
1240 						}
1241 					} else {
1242 						if(g_hash_table_lookup(video_extmaps, extmap) != NULL) {
1243 							JANUS_LOG(LOG_WARN, "Ignoring duplicate video extension %s (already added: %d)\n",
1244 								extmap, GPOINTER_TO_INT(g_hash_table_lookup(video_extmaps, extmap)));
1245 						} else {
1246 							g_hash_table_insert(video_extmaps, extmap, GINT_TO_POINTER(id));
1247 							g_hash_table_insert(video_extids, GINT_TO_POINTER(id), extmap);
1248 						}
1249 					}
1250 				}
1251 			}
1252 		} else {
1253 			JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property);
1254 		}
1255 		property = va_arg(args, int);
1256 	}
1257 	if(audio_codec == NULL)
1258 		audio_codec = "opus";
1259 	const char *audio_rtpmap = do_audio ? janus_sdp_get_codec_rtpmap(audio_codec) : NULL;
1260 	if(do_audio && audio_rtpmap == NULL) {
1261 		JANUS_LOG(LOG_ERR, "Unsupported audio codec '%s', can't prepare an offer\n", audio_codec);
1262 		va_end(args);
1263 		if(audio_extmaps != NULL)
1264 			g_hash_table_destroy(audio_extmaps);
1265 		if(audio_extids != NULL)
1266 			g_hash_table_destroy(audio_extids);
1267 		if(video_extmaps != NULL)
1268 			g_hash_table_destroy(video_extmaps);
1269 		if(video_extids != NULL)
1270 			g_hash_table_destroy(video_extids);
1271 		return NULL;
1272 	}
1273 	if(video_codec == NULL)
1274 		video_codec = "vp8";
1275 	const char *video_rtpmap = do_video ? janus_sdp_get_codec_rtpmap(video_codec) : NULL;
1276 	if(do_video && video_rtpmap == NULL) {
1277 		JANUS_LOG(LOG_ERR, "Unsupported video codec '%s', can't prepare an offer\n", video_codec);
1278 		va_end(args);
1279 		if(audio_extmaps != NULL)
1280 			g_hash_table_destroy(audio_extmaps);
1281 		if(audio_extids != NULL)
1282 			g_hash_table_destroy(audio_extids);
1283 		if(video_extmaps != NULL)
1284 			g_hash_table_destroy(video_extmaps);
1285 		if(video_extids != NULL)
1286 			g_hash_table_destroy(video_extids);
1287 		return NULL;
1288 	}
1289 #ifndef HAVE_SCTP
1290 	do_data = FALSE;
1291 #endif
1292 
1293 	/* Create a new janus_sdp object */
1294 	janus_sdp *offer = janus_sdp_new(name, address);
1295 	/* Now add all the media we should */
1296 	if(do_audio) {
1297 		janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_AUDIO, 1, "UDP/TLS/RTP/SAVPF", audio_dir);
1298 		m->c_ipv4 = TRUE;
1299 		m->c_addr = g_strdup(offer->c_addr);
1300 		/* Add the selected audio codec */
1301 		m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(audio_pt));
1302 		janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", audio_pt, audio_rtpmap);
1303 		m->attributes = g_list_append(m->attributes, a);
1304 		/* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */
1305 		if(audio_dtmf) {
1306 			/* We do */
1307 			int dtmf_pt = 126;
1308 			m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(dtmf_pt));
1309 			janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf"));
1310 			m->attributes = g_list_append(m->attributes, a);
1311 		}
1312 		/* Check if there's a custom fmtp line to add for audio */
1313 		if(audio_fmtp) {
1314 			janus_sdp_attribute *a = janus_sdp_attribute_create("fmtp", "%d %s", audio_pt, audio_fmtp);
1315 			m->attributes = g_list_append(m->attributes, a);
1316 		}
1317 		/* Check if we need to add audio extensions to the SDP */
1318 		if(audio_extids != NULL) {
1319 			GList *ids = g_list_sort(g_hash_table_get_keys(audio_extids), janus_sdp_id_compare), *iter = ids;
1320 			while(iter) {
1321 				char *extmap = g_hash_table_lookup(audio_extids, iter->data);
1322 				if(extmap != NULL) {
1323 					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
1324 						"%d %s", GPOINTER_TO_INT(iter->data), extmap);
1325 					janus_sdp_attribute_add_to_mline(m, a);
1326 				}
1327 				iter = iter->next;
1328 			}
1329 			g_list_free(ids);
1330 		}
1331 		/* It is safe to add transport-wide rtcp feedback message here, won't be used unless the header extension is negotiated */
1332 		a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", audio_pt);
1333 		m->attributes = g_list_append(m->attributes, a);
1334 		offer->m_lines = g_list_append(offer->m_lines, m);
1335 	}
1336 	if(do_video) {
1337 		janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_VIDEO, 1, "UDP/TLS/RTP/SAVPF", video_dir);
1338 		m->c_ipv4 = TRUE;
1339 		m->c_addr = g_strdup(offer->c_addr);
1340 		/* Add the selected video codec */
1341 		m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(video_pt));
1342 		janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", video_pt, video_rtpmap);
1343 		m->attributes = g_list_append(m->attributes, a);
1344 		if(video_rtcpfb) {
1345 			/* Add rtcp-fb attributes */
1346 			a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", video_pt);
1347 			m->attributes = g_list_append(m->attributes, a);
1348 			a = janus_sdp_attribute_create("rtcp-fb", "%d nack", video_pt);
1349 			m->attributes = g_list_append(m->attributes, a);
1350 			a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", video_pt);
1351 			m->attributes = g_list_append(m->attributes, a);
1352 			a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", video_pt);
1353 			m->attributes = g_list_append(m->attributes, a);
1354 		}
1355 		/* It is safe to add transport-wide rtcp feedback message here, won't be used unless the header extension is negotiated */
1356 		a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", video_pt);
1357 		m->attributes = g_list_append(m->attributes, a);
1358 		/* Check if we need to add audio extensions to the SDP */
1359 		if(video_extids != NULL) {
1360 			GList *ids = g_list_sort(g_hash_table_get_keys(video_extids), janus_sdp_id_compare), *iter = ids;
1361 			while(iter) {
1362 				char *extmap = g_hash_table_lookup(video_extids, iter->data);
1363 				if(extmap != NULL) {
1364 					janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
1365 						"%d %s", GPOINTER_TO_INT(iter->data), extmap);
1366 					janus_sdp_attribute_add_to_mline(m, a);
1367 				}
1368 				iter = iter->next;
1369 			}
1370 			g_list_free(ids);
1371 		}
1372 		if(!strcasecmp(video_codec, "vp9") && vp9_profile) {
1373 			/* Add a profile-id fmtp attribute */
1374 			a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", video_pt, vp9_profile);
1375 			m->attributes = g_list_append(m->attributes, a);
1376 		} else if(!strcasecmp(video_codec, "h264") && h264_profile) {
1377 			/* Add a profile-level-id fmtp attribute */
1378 			a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", video_pt, h264_profile);
1379 			m->attributes = g_list_append(m->attributes, a);
1380 		} else if(video_fmtp) {
1381 			/* There's a custom fmtp line to add for video */
1382 			a = janus_sdp_attribute_create("fmtp", "%d %s", video_pt, video_fmtp);
1383 			m->attributes = g_list_append(m->attributes, a);
1384 		}
1385 		offer->m_lines = g_list_append(offer->m_lines, m);
1386 	}
1387 	if(do_data) {
1388 		janus_sdp_mline *m = janus_sdp_mline_create(JANUS_SDP_APPLICATION, 1,
1389 			data_legacy ? "DTLS/SCTP" : "UDP/DTLS/SCTP", JANUS_SDP_DEFAULT);
1390 		m->c_ipv4 = TRUE;
1391 		m->c_addr = g_strdup(offer->c_addr);
1392 		m->fmts = g_list_append(m->fmts, g_strdup(data_legacy ? "5000" : "webrtc-datachannel"));
1393 		/* Add an sctpmap attribute */
1394 		if(data_legacy) {
1395 			janus_sdp_attribute *aa = janus_sdp_attribute_create("sctpmap", "5000 webrtc-datachannel 16");
1396 			m->attributes = g_list_append(m->attributes, aa);
1397 		} else {
1398 			janus_sdp_attribute *aa = janus_sdp_attribute_create("sctp-port", "5000");
1399 			m->attributes = g_list_append(m->attributes, aa);
1400 		}
1401 		offer->m_lines = g_list_append(offer->m_lines, m);
1402 	}
1403 	if(audio_extmaps != NULL)
1404 		g_hash_table_destroy(audio_extmaps);
1405 	if(audio_extids != NULL)
1406 		g_hash_table_destroy(audio_extids);
1407 	if(video_extmaps != NULL)
1408 		g_hash_table_destroy(video_extmaps);
1409 	if(video_extids != NULL)
1410 		g_hash_table_destroy(video_extids);
1411 
1412 	/* Done */
1413 	va_end(args);
1414 
1415 	return offer;
1416 }
1417 
janus_sdp_generate_answer(janus_sdp * offer,...)1418 janus_sdp *janus_sdp_generate_answer(janus_sdp *offer, ...) {
1419 	if(offer == NULL)
1420 		return NULL;
1421 
1422 	janus_refcount_increase(&offer->ref);
1423 	/* This method has a variable list of arguments, telling us how we should respond */
1424 	va_list args;
1425 	va_start(args, offer);
1426 	/* Let's see what we should do with the media */
1427 	gboolean do_audio = TRUE, do_video = TRUE, do_data = TRUE,
1428 		audio_dtmf = FALSE, video_rtcpfb = TRUE;
1429 	const char *audio_codec = NULL, *video_codec = NULL,
1430 		*vp9_profile = NULL, *h264_profile = NULL,
1431 		*audio_fmtp = NULL, *video_fmtp = NULL;
1432 	char *custom_audio_fmtp = NULL;
1433 	GList *extmaps = NULL;
1434 	janus_sdp_mdirection audio_dir = JANUS_SDP_SENDRECV, video_dir = JANUS_SDP_SENDRECV;
1435 	int property = va_arg(args, int);
1436 	while(property != JANUS_SDP_OA_DONE) {
1437 		if(property == JANUS_SDP_OA_AUDIO) {
1438 			do_audio = va_arg(args, gboolean);
1439 		} else if(property == JANUS_SDP_OA_VIDEO) {
1440 			do_video = va_arg(args, gboolean);
1441 		} else if(property == JANUS_SDP_OA_DATA) {
1442 			do_data = va_arg(args, gboolean);
1443 		} else if(property == JANUS_SDP_OA_AUDIO_DIRECTION) {
1444 			audio_dir = va_arg(args, janus_sdp_mdirection);
1445 		} else if(property == JANUS_SDP_OA_VIDEO_DIRECTION) {
1446 			video_dir = va_arg(args, janus_sdp_mdirection);
1447 		} else if(property == JANUS_SDP_OA_AUDIO_CODEC) {
1448 			audio_codec = va_arg(args, char *);
1449 		} else if(property == JANUS_SDP_OA_VIDEO_CODEC) {
1450 			video_codec = va_arg(args, char *);
1451 		} else if(property == JANUS_SDP_OA_VP9_PROFILE) {
1452 			vp9_profile = va_arg(args, char *);
1453 		} else if(property == JANUS_SDP_OA_H264_PROFILE) {
1454 			h264_profile = va_arg(args, char *);
1455 		} else if(property == JANUS_SDP_OA_AUDIO_DTMF) {
1456 			audio_dtmf = va_arg(args, gboolean);
1457 		} else if(property == JANUS_SDP_OA_AUDIO_FMTP) {
1458 			audio_fmtp = va_arg(args, char *);
1459 		} else if(property == JANUS_SDP_OA_VIDEO_FMTP) {
1460 			video_fmtp = va_arg(args, char *);
1461 		} else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) {
1462 			video_rtcpfb = va_arg(args, gboolean);
1463 		} else if(property == JANUS_SDP_OA_ACCEPT_EXTMAP) {
1464 			const char *extension = va_arg(args, char *);
1465 			if(extension != NULL)
1466 				extmaps = g_list_append(extmaps, (char *)extension);
1467 		} else {
1468 			JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property);
1469 		}
1470 		property = va_arg(args, int);
1471 	}
1472 #ifndef HAVE_SCTP
1473 	do_data = FALSE;
1474 #endif
1475 
1476 	janus_sdp *answer = g_malloc(sizeof(janus_sdp));
1477 	g_atomic_int_set(&answer->destroyed, 0);
1478 	janus_refcount_init(&answer->ref, janus_sdp_free);
1479 	/* Start by copying some of the headers */
1480 	answer->version = offer->version;
1481 	answer->o_name = g_strdup(offer->o_name ? offer->o_name : "-");
1482 	answer->o_sessid = offer->o_sessid;
1483 	answer->o_version = offer->o_version;
1484 	answer->o_ipv4 = offer->o_ipv4;
1485 	answer->o_addr = g_strdup(offer->o_addr ? offer->o_addr : "127.0.0.1");
1486 	answer->s_name = g_strdup(offer->s_name ? offer->s_name : "Janus session");
1487 	answer->t_start = 0;
1488 	answer->t_stop = 0;
1489 	answer->c_ipv4 = offer->c_ipv4;
1490 	answer->c_addr = g_strdup(offer->c_addr ? offer->c_addr : "127.0.0.1");
1491 	answer->attributes = NULL;
1492 	answer->m_lines = NULL;
1493 
1494 	/* Now iterate on all media, and let's see what we should do */
1495 	int audio = 0, video = 0, data = 0;
1496 	GList *temp = offer->m_lines;
1497 	while(temp) {
1498 		janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
1499 		/* For each m-line we parse, we'll need a corresponding one in the answer */
1500 		janus_sdp_mline *am = g_malloc0(sizeof(janus_sdp_mline));
1501 		g_atomic_int_set(&am->destroyed, 0);
1502 		janus_refcount_init(&am->ref, janus_sdp_mline_free);
1503 		am->type = m->type;
1504 		am->type_str = m->type_str ? g_strdup(m->type_str) : NULL;
1505 		am->proto = g_strdup(m->proto ? m->proto : "UDP/TLS/RTP/SAVPF");
1506 		am->port = m->port;
1507 		am->c_ipv4 = m->c_ipv4;
1508 		am->c_addr = g_strdup(am->c_addr ? am->c_addr : "127.0.0.1");
1509 		am->direction = JANUS_SDP_INACTIVE;	/* We'll change this later */
1510 		/* Append to the list of m-lines in the answer */
1511 		answer->m_lines = g_list_append(answer->m_lines, am);
1512 		/* Let's see what this is */
1513 		if(m->type == JANUS_SDP_AUDIO) {
1514 			if(m->port > 0) {
1515 				audio++;
1516 			}
1517 			if(!do_audio || audio > 1) {
1518 				/* Reject */
1519 				if(audio > 1)
1520 					am->port = 0;
1521 				temp = temp->next;
1522 				continue;
1523 			}
1524 		} else if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
1525 			if(m->port > 0) {
1526 				video++;
1527 			}
1528 			if(!do_video || video > 1) {
1529 				/* Reject */
1530 				if(video > 1)
1531 					am->port = 0;
1532 				temp = temp->next;
1533 				continue;
1534 			}
1535 		} else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
1536 			if(m->port > 0) {
1537 				data++;
1538 			}
1539 			if(!do_data || data > 1) {
1540 				/* Reject */
1541 				am->port = 0;
1542 				/* Add the format anyway, to keep Firefox happy */
1543 				GList *fmt = m->fmts;
1544 				if(fmt) {
1545 					char *fmt_str = (char *)fmt->data;
1546 					if(fmt_str)
1547 						am->fmts = g_list_append(am->fmts, g_strdup(fmt_str));
1548 				}
1549 				temp = temp->next;
1550 				continue;
1551 			}
1552 		}
1553 		if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
1554 			janus_sdp_mdirection target_dir = m->type == JANUS_SDP_AUDIO ? audio_dir : video_dir;
1555 			/* What is the direction we were offered? And how were we asked to react?
1556 			 * Adapt the direction in our answer accordingly */
1557 			switch(m->direction) {
1558 				case JANUS_SDP_RECVONLY:
1559 					if(target_dir == JANUS_SDP_SENDRECV || target_dir == JANUS_SDP_SENDONLY) {
1560 						/* Peer is recvonly, we'll only send */
1561 						am->direction = JANUS_SDP_SENDONLY;
1562 					} else {
1563 						/* Peer is recvonly, but we're not ok to send, so reply with inactive */
1564 						JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n",
1565 							m->type == JANUS_SDP_AUDIO ? "Audio" : "Video",
1566 							janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir));
1567 						am->direction = JANUS_SDP_INACTIVE;
1568 					}
1569 					break;
1570 				case JANUS_SDP_SENDONLY:
1571 					if(target_dir == JANUS_SDP_SENDRECV || target_dir == JANUS_SDP_RECVONLY) {
1572 						/* Peer is sendonly, we'll only receive */
1573 						am->direction = JANUS_SDP_RECVONLY;
1574 					} else {
1575 						/* Peer is sendonly, but we're not ok to receive, so reply with inactive */
1576 						JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n",
1577 							m->type == JANUS_SDP_AUDIO ? "Audio" : "Video",
1578 							janus_sdp_mdirection_str(m->direction), janus_sdp_mdirection_str(target_dir));
1579 						am->direction = JANUS_SDP_INACTIVE;
1580 					}
1581 					break;
1582 				case JANUS_SDP_INACTIVE:
1583 					/* Peer inactive, set inactive in the answer to */
1584 					am->direction = JANUS_SDP_INACTIVE;
1585 					break;
1586 				case JANUS_SDP_SENDRECV:
1587 				default:
1588 					/* The peer is fine with everything, so use our constraint */
1589 					am->direction = target_dir;
1590 					break;
1591 			}
1592 			/* Look for the right codec and stick to that */
1593 			const char *codec = m->type == JANUS_SDP_AUDIO ? audio_codec : video_codec;
1594 			if(codec == NULL) {
1595 				/* FIXME User didn't provide a codec to accept? Let's see if Opus (for audio)
1596 				 * of VP8 (for video) were negotiated: if so, use them, otherwise let's
1597 				 * pick some other codec we know about among the ones that were offered.
1598 				 * Notice that if it's not a codec we understand, we reject the medium,
1599 				 * as browsers would reject it anyway. If you need more flexibility you'll
1600 				 * have to generate an answer yourself, rather than automatically... */
1601 				codec = m->type == JANUS_SDP_AUDIO ? "opus" : "vp8";
1602 				if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1603 					/* We couldn't find our preferred codec, let's try something else */
1604 					if(m->type == JANUS_SDP_AUDIO) {
1605 						/* Opus not found, maybe mu-law? */
1606 						codec = "pcmu";
1607 						if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1608 							/* mu-law not found, maybe a-law? */
1609 							codec = "pcma";
1610 							if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1611 								/* a-law not found, maybe G.722? */
1612 								codec = "g722";
1613 								if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1614 									/* G.722 not found, maybe isac32? */
1615 									codec = "isac32";
1616 									if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1617 										/* isac32 not found, maybe isac16? */
1618 										codec = "isac16";
1619 										if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1620 											/* isac16 not found, maybe multiopus? */
1621 											codec = "multiopus";
1622 										}
1623 									}
1624 								}
1625 							}
1626 						}
1627 					} else {
1628 						/* VP8 not found, maybe VP9? */
1629 						codec = "vp9";
1630 						if(janus_sdp_get_codec_pt_full(offer, codec, vp9_profile) < 0) {
1631 							/* VP9 not found either, maybe H.264? */
1632 							codec = "h264";
1633 							if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1634 								/* H.264 not found either, maybe AV1? */
1635 								codec = "av1";
1636 								if(janus_sdp_get_codec_pt(offer, codec) < 0) {
1637 									/* AV1 not found either, maybe H.265? */
1638 									codec = "h265";
1639 								}
1640 							}
1641 						}
1642 					}
1643 				}
1644 			}
1645 			const char *video_profile = NULL;
1646 			if(codec && !strcasecmp(codec, "vp9"))
1647 				video_profile = vp9_profile;
1648 			else if(codec && !strcasecmp(codec, "h264"))
1649 				video_profile = h264_profile;
1650 			int pt = janus_sdp_get_codec_pt_full(offer, codec, video_profile);
1651 			if(pt < 0) {
1652 				/* Reject */
1653 				JANUS_LOG(LOG_WARN, "Couldn't find codec we needed (%s) in the offer, rejecting %s\n",
1654 					codec, m->type == JANUS_SDP_AUDIO ? "audio" : "video");
1655 				am->port = 0;
1656 				am->direction = JANUS_SDP_INACTIVE;
1657 				temp = temp->next;
1658 				continue;
1659 			}
1660 			if(!strcasecmp(codec, "multiopus") && (audio_fmtp == NULL ||
1661 					strstr(audio_fmtp, "channel_mapping") == NULL)) {
1662 				/* Missing channel mapping for the multiopus m-line, check the offer */
1663 				GList *mo = m->attributes;
1664 				while(mo) {
1665 					janus_sdp_attribute *a = (janus_sdp_attribute *)mo->data;
1666 					if(a->name && strstr(a->name, "fmtp") && a->value) {
1667 						char *tmp = strchr(a->value, ' ');
1668 						if(tmp && strlen(tmp) > 1 && custom_audio_fmtp == NULL) {
1669 							tmp++;
1670 							custom_audio_fmtp = g_strdup(tmp);
1671 							/* FIXME We should integrate the existing audio_fmtp */
1672 						}
1673 						break;
1674 					}
1675 					mo = mo->next;
1676 				}
1677 			}
1678 			am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(pt));
1679 			/* Add the related attributes */
1680 			if(m->type == JANUS_SDP_AUDIO) {
1681 				/* Add rtpmap attribute */
1682 				const char *codec_rtpmap = janus_sdp_get_codec_rtpmap(codec);
1683 				if(codec_rtpmap) {
1684 					janus_sdp_attribute *a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, codec_rtpmap);
1685 					am->attributes = g_list_append(am->attributes, a);
1686 					/* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */
1687 					if(audio_dtmf) {
1688 						int dtmf_pt = janus_sdp_get_codec_pt(offer, "dtmf");
1689 						if(dtmf_pt >= 0) {
1690 							/* We do */
1691 							am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(dtmf_pt));
1692 							a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf"));
1693 							am->attributes = g_list_append(am->attributes, a);
1694 						}
1695 					}
1696 					/* Check if there's a custom fmtp line to add for audio
1697 					 * FIXME We should actually check if it matches the offer */
1698 					if(audio_fmtp || custom_audio_fmtp) {
1699 						a = janus_sdp_attribute_create("fmtp", "%d %s",
1700 							pt, custom_audio_fmtp ? custom_audio_fmtp : audio_fmtp);
1701 						am->attributes = g_list_append(am->attributes, a);
1702 					}
1703 				}
1704 			} else {
1705 				/* Add rtpmap attribute */
1706 				const char *codec_rtpmap = janus_sdp_get_codec_rtpmap(codec);
1707 				janus_sdp_attribute *a = NULL;
1708 				if(codec_rtpmap) {
1709 					a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, codec_rtpmap);
1710 					am->attributes = g_list_append(am->attributes, a);
1711 					if(video_rtcpfb) {
1712 						/* Add rtcp-fb attributes */
1713 						a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", pt);
1714 						am->attributes = g_list_append(am->attributes, a);
1715 						a = janus_sdp_attribute_create("rtcp-fb", "%d nack", pt);
1716 						am->attributes = g_list_append(am->attributes, a);
1717 						a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", pt);
1718 						am->attributes = g_list_append(am->attributes, a);
1719 						a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", pt);
1720 						am->attributes = g_list_append(am->attributes, a);
1721 					}
1722 					/* It is safe to add transport-wide rtcp feedback mesage here, won't be used unless the header extension is negotiated*/
1723 					a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", pt);
1724 					am->attributes = g_list_append(am->attributes, a);
1725 				}
1726 				if(!strcasecmp(codec, "vp9") && vp9_profile) {
1727 					/* Add a profile-id fmtp attribute */
1728 					a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", pt, vp9_profile);
1729 					am->attributes = g_list_append(am->attributes, a);
1730 				} else if(!strcasecmp(codec, "h264") && h264_profile) {
1731 					/* Add a profile-level-id fmtp attribute */
1732 					a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", pt, h264_profile);
1733 					am->attributes = g_list_append(am->attributes, a);
1734 				} else if(video_fmtp) {
1735 					/* There's a custom fmtp line to add for video
1736 					 * FIXME We should actually check if it matches the offer */
1737 					a = janus_sdp_attribute_create("fmtp", "%d %s", pt, video_fmtp);
1738 					am->attributes = g_list_append(am->attributes, a);
1739 				}
1740 			}
1741 			/* Add the extmap attributes, if needed */
1742 			if(extmaps != NULL) {
1743 				GList *ma = m->attributes;
1744 				while(ma) {
1745 					/* Iterate on all attributes, to see if there's an extension to accept */
1746 					janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
1747 					if(a->name && strstr(a->name, "extmap") && a->value) {
1748 						GList *temp = extmaps;
1749 						while(temp != NULL) {
1750 							char *extension = (char *)temp->data;
1751 							if(strstr(a->value, extension)) {
1752 								/* Accept the extension */
1753 								int id = atoi(a->value);
1754 								if(id < 0) {
1755 									JANUS_LOG(LOG_ERR, "Invalid extension ID (%d)\n", id);
1756 									temp = temp->next;
1757 									continue;
1758 								}
1759 								const char *direction = NULL;
1760 								switch(a->direction) {
1761 									case JANUS_SDP_SENDONLY:
1762 										direction = "/recvonly";
1763 										break;
1764 									case JANUS_SDP_RECVONLY:
1765 									case JANUS_SDP_INACTIVE:
1766 										direction = "/inactive";
1767 										break;
1768 									default:
1769 										direction = "";
1770 										break;
1771 								}
1772 								a = janus_sdp_attribute_create("extmap",
1773 									"%d%s %s", id, direction, extension);
1774 								janus_sdp_attribute_add_to_mline(am, a);
1775 							}
1776 							temp = temp->next;
1777 						}
1778 					} else if(m->type == JANUS_SDP_VIDEO && a->name && strstr(a->name, "fmtp") && a->value && atoi(a->value) == pt) {
1779 						/* Check if we need to copy the fmtp attribute too */
1780 						if(((!strcasecmp(codec, "vp8") && video_fmtp == NULL)) ||
1781 								((!strcasecmp(codec, "vp9") && vp9_profile == NULL && video_fmtp == NULL)) ||
1782 								((!strcasecmp(codec, "h264") && h264_profile == NULL && video_fmtp == NULL))) {
1783 							/* FIXME Copy the fmtp attribute (we should check if we support it) */
1784 							a = janus_sdp_attribute_create("fmtp", "%s", a->value);
1785 							janus_sdp_attribute_add_to_mline(am, a);
1786 						}
1787 					}
1788 					ma = ma->next;
1789 				}
1790 			}
1791 		} else {
1792 			/* This is for data, add formats and an sctpmap attribute */
1793 			am->direction = JANUS_SDP_DEFAULT;
1794 			GList *fmt = m->fmts;
1795 			while(fmt) {
1796 				char *fmt_str = (char *)fmt->data;
1797 				if(fmt_str)
1798 					am->fmts = g_list_append(am->fmts, g_strdup(fmt_str));
1799 				fmt = fmt->next;
1800 			}
1801 		}
1802 		temp = temp->next;
1803 	}
1804 	janus_refcount_decrease(&offer->ref);
1805 
1806 	/* Done */
1807 	g_list_free(extmaps);
1808 	g_free(custom_audio_fmtp);
1809 	va_end(args);
1810 
1811 	return answer;
1812 }
1813