utils: add g_slist_free_full() replacement
[siplcs.git] / src / core / sdpmsg.c
blobecf4de8181ceb23c54279deb58dc24d0eb100e77
1 /**
2 * @file sdpmsg.c
4 * pidgin-sipe
6 * Copyright (C) 2013 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 Jakub Adam <jakub.adam@ktknet.cz>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #include <glib.h>
30 #include "sipe-backend.h"
31 #include "sdpmsg.h"
32 #include "sipe-utils.h"
34 static gboolean
35 append_attribute(struct sdpmedia *media, gchar *attr)
37 gchar **parts = g_strsplit(attr + 2, ":", 2);
39 if(!parts[0]) {
40 g_strfreev(parts);
41 return FALSE;
44 media->attributes = sipe_utils_nameval_add(media->attributes,
45 parts[0],
46 parts[1] ? parts[1] : "");
47 g_strfreev(parts);
48 return TRUE;
51 static gboolean
52 parse_attributes(struct sdpmsg *smsg, gchar *msg) {
53 gchar **lines = g_strsplit(msg, "\r\n", 0);
54 gchar **ptr = lines;
56 while (*ptr != NULL) {
57 if (g_str_has_prefix(*ptr, "o=")) {
58 gchar **parts = g_strsplit(*ptr + 2, " ", 6);
59 smsg->ip = g_strdup(parts[5]);
60 g_strfreev(parts);
61 } else if (g_str_has_prefix(*ptr, "m=")) {
62 gchar **parts = g_strsplit(*ptr + 2, " ", 3);
63 struct sdpmedia *media = g_new0(struct sdpmedia, 1);
65 smsg->media = g_slist_append(smsg->media, media);
67 media->name = g_strdup(parts[0]);
68 media->port = atoi(parts[1]);
70 g_strfreev(parts);
72 while (*(++ptr) && !g_str_has_prefix(*ptr, "m=")) {
74 if (g_str_has_prefix(*ptr, "a=")) {
75 if (!append_attribute(media, *ptr)) {
76 g_strfreev(lines);
77 return FALSE;
81 continue;
84 ++ptr;
87 g_strfreev(lines);
89 return TRUE;
92 static struct sdpcandidate * sdpcandidate_copy(struct sdpcandidate *candidate);
93 static void sdpcandidate_free(struct sdpcandidate *candidate);
95 static SipeComponentType
96 parse_component(const gchar *str)
98 switch (atoi(str)) {
99 case 1: return SIPE_COMPONENT_RTP;
100 case 2: return SIPE_COMPONENT_RTCP;
101 default: return SIPE_COMPONENT_NONE;
105 static gchar *
106 base64_pad(const gchar* str)
108 size_t str_len = strlen(str);
109 int mod = str_len % 4;
111 if (mod > 0) {
112 gchar *result = NULL;
113 int pad = 4 - mod;
114 gchar *ptr = result = g_malloc(str_len + pad + 1);
116 memcpy(ptr, str, str_len);
117 ptr += str_len;
118 memset(ptr, '=', pad);
119 ptr += pad;
120 *ptr = '\0';
122 return result;
123 } else
124 return g_strdup(str);
127 static GSList *
128 parse_append_candidate_draft_6(gchar **tokens, GSList *candidates)
130 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
132 candidate->username = base64_pad(tokens[0]);
133 candidate->component = parse_component(tokens[1]);
134 candidate->password = base64_pad(tokens[2]);
136 if (sipe_strequal(tokens[3], "UDP"))
137 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
138 else if (sipe_strequal(tokens[3], "TCP"))
139 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
140 else {
141 sdpcandidate_free(candidate);
142 return candidates;
145 candidate->priority = atoi(tokens[4] + 2);
146 candidate->ip = g_strdup(tokens[5]);
147 candidate->port = atoi(tokens[6]);
149 candidates = g_slist_append(candidates, candidate);
151 // draft 6 candidates are both active and passive
152 if (candidate->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE) {
153 candidate = sdpcandidate_copy(candidate);
154 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
155 candidates = g_slist_append(candidates, candidate);
158 return candidates;
161 static GSList *
162 parse_append_candidate_rfc_5245(gchar **tokens, GSList *candidates)
164 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
166 candidate->foundation = g_strdup(tokens[0]);
167 candidate->component = parse_component(tokens[1]);
169 if (sipe_strequal(tokens[2], "UDP"))
170 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
171 else if (sipe_strequal(tokens[2], "TCP-ACT"))
172 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
173 else if (sipe_strequal(tokens[2], "TCP-PASS"))
174 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
175 else {
176 sdpcandidate_free(candidate);
177 return candidates;
180 candidate->priority = atoi(tokens[3]);
181 candidate->ip = g_strdup(tokens[4]);
182 candidate->port = atoi(tokens[5]);
184 if (sipe_strequal(tokens[7], "host"))
185 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
186 else if (sipe_strequal(tokens[7], "relay"))
187 candidate->type = SIPE_CANDIDATE_TYPE_RELAY;
188 else if (sipe_strequal(tokens[7], "srflx"))
189 candidate->type = SIPE_CANDIDATE_TYPE_SRFLX;
190 else if (sipe_strequal(tokens[7], "prflx"))
191 candidate->type = SIPE_CANDIDATE_TYPE_PRFLX;
192 else {
193 sdpcandidate_free(candidate);
194 return candidates;
197 return g_slist_append(candidates, candidate);
200 static GSList *
201 parse_candidates(GSList *attrs, SipeIceVersion *ice_version)
203 GSList *candidates = NULL;
204 const gchar *attr;
205 int i = 0;
207 while ((attr = sipe_utils_nameval_find_instance(attrs, "candidate", i++))) {
208 gchar **tokens = g_strsplit_set(attr, " ", 0);
210 if (sipe_strequal(tokens[6], "typ")) {
211 candidates = parse_append_candidate_rfc_5245(tokens, candidates);
212 if (candidates)
213 *ice_version = SIPE_ICE_RFC_5245;
214 } else {
215 candidates = parse_append_candidate_draft_6(tokens, candidates);
216 if (candidates)
217 *ice_version = SIPE_ICE_DRAFT_6;
220 g_strfreev(tokens);
223 if (!candidates)
224 *ice_version = SIPE_ICE_NO_ICE;
226 if (*ice_version == SIPE_ICE_RFC_5245) {
227 const gchar *username = sipe_utils_nameval_find(attrs, "ice-ufrag");
228 const gchar *password = sipe_utils_nameval_find(attrs, "ice-pwd");
230 if (username && password) {
231 GSList *i;
232 for (i = candidates; i; i = i->next) {
233 struct sdpcandidate *c = i->data;
234 c->username = g_strdup(username);
235 c->password = g_strdup(password);
240 return candidates;
243 static GSList *
244 create_legacy_candidates(gchar *ip, guint16 port)
246 struct sdpcandidate *candidate;
247 GSList *candidates = NULL;
249 candidate = g_new0(struct sdpcandidate, 1);
250 candidate->foundation = g_strdup("1");
251 candidate->component = SIPE_COMPONENT_RTP;
252 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
253 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
254 candidate->ip = g_strdup(ip);
255 candidate->port = port;
257 candidates = g_slist_append(candidates, candidate);
259 candidate = g_new0(struct sdpcandidate, 1);
260 candidate->foundation = g_strdup("1");
261 candidate->component = SIPE_COMPONENT_RTCP;
262 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
263 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
264 candidate->ip = g_strdup(ip);
265 candidate->port = port + 1;
267 candidates = g_slist_append(candidates, candidate);
269 return candidates;
272 static GSList *
273 parse_codecs(GSList *attrs, SipeMediaType type)
275 int i = 0;
276 const gchar *attr;
277 GSList *codecs = NULL;
279 while ((attr = sipe_utils_nameval_find_instance(attrs, "rtpmap", i++))) {
280 struct sdpcodec *codec = g_new0(struct sdpcodec, 1);
281 gchar **tokens = g_strsplit_set(attr, " /", 3);
283 int j = 0;
284 const gchar* params;
286 codec->id = atoi(tokens[0]);
287 codec->name = g_strdup(tokens[1]);
288 codec->clock_rate = atoi(tokens[2]);
289 codec->type = type;
291 // TODO: more secure and effective implementation
292 while((params = sipe_utils_nameval_find_instance(attrs, "fmtp", j++))) {
293 gchar **tokens = g_strsplit_set(params, " ", 0);
294 gchar **next = tokens + 1;
296 if (atoi(tokens[0]) == codec->id) {
297 while (*next) {
298 gchar name[50];
299 gchar value[50];
301 if (sscanf(*next, "%[a-zA-Z0-9]=%s", name, value) == 2)
302 codec->parameters = sipe_utils_nameval_add(codec->parameters, name, value);
304 ++next;
308 g_strfreev(tokens);
311 codecs = g_slist_append(codecs, codec);
312 g_strfreev(tokens);
315 return codecs;
318 struct sdpmsg *
319 sdpmsg_parse_msg(gchar *msg)
321 struct sdpmsg *smsg = g_new0(struct sdpmsg, 1);
322 GSList *i;
324 if (!parse_attributes(smsg, msg)) {
325 sdpmsg_free(smsg);
326 return NULL;
329 for (i = smsg->media; i; i = i->next) {
330 struct sdpmedia *media = i->data;
331 SipeMediaType type;
333 media->candidates = parse_candidates(media->attributes,
334 &smsg->ice_version);
336 if (!media->candidates && media->port != 0) {
337 // No a=candidate in SDP message, this seems to be MSOC 2005
338 media->candidates = create_legacy_candidates(smsg->ip, media->port);
341 if (sipe_strequal(media->name, "audio"))
342 type = SIPE_MEDIA_AUDIO;
343 else if (sipe_strequal(media->name, "video"))
344 type = SIPE_MEDIA_VIDEO;
345 else {
346 // Unknown media type
347 sdpmsg_free(smsg);
348 return NULL;
351 media->codecs = parse_codecs(media->attributes, type);
354 return smsg;
357 static gchar *
358 codecs_to_string(GSList *codecs)
360 GString *result = g_string_new(NULL);
362 for (; codecs; codecs = codecs->next) {
363 struct sdpcodec *c = codecs->data;
364 GSList *params = c->parameters;
366 g_string_append_printf(result,
367 "a=rtpmap:%d %s/%d\r\n",
368 c->id,
369 c->name,
370 c->clock_rate);
372 if (params) {
373 g_string_append_printf(result, "a=fmtp:%d", c->id);
375 for (; params; params = params->next) {
376 struct sipnameval* par = params->data;
377 g_string_append_printf(result, " %s=%s",
378 par->name, par->value);
381 g_string_append(result, "\r\n");
385 return g_string_free(result, FALSE);
388 static gchar *
389 codec_ids_to_string(GSList *codecs)
391 GString *result = g_string_new(NULL);
393 for (; codecs; codecs = codecs->next) {
394 struct sdpcodec *c = codecs->data;
395 g_string_append_printf(result, " %d", c->id);
398 return g_string_free(result, FALSE);
401 static gchar *
402 base64_unpad(const gchar *str)
404 gchar *result = g_strdup(str);
405 gchar *ptr;
407 for (ptr = result + strlen(result); ptr != result; --ptr) {
408 if (*(ptr - 1) != '=') {
409 *ptr = '\0';
410 break;
414 return result;
417 static gint
418 candidate_sort_cb(struct sdpcandidate *c1, struct sdpcandidate *c2)
420 int cmp = sipe_strcompare(c1->foundation, c2->foundation);
421 if (cmp == 0) {
422 cmp = sipe_strcompare(c1->username, c2->username);
423 if (cmp == 0)
424 cmp = c1->component - c2->component;
427 return cmp;
430 static gchar *
431 candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
433 GString *result = g_string_new("");
434 GSList *i;
435 GSList *processed_tcp_candidates = NULL;
437 candidates = g_slist_copy(candidates);
438 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
440 for (i = candidates; i; i = i->next) {
441 struct sdpcandidate *c = i->data;
442 const gchar *protocol;
443 const gchar *type;
444 gchar *related = NULL;
446 if (ice_version == SIPE_ICE_RFC_5245) {
448 switch (c->protocol) {
449 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
450 protocol = "TCP-ACT";
451 break;
452 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
453 protocol = "TCP-PASS";
454 break;
455 case SIPE_NETWORK_PROTOCOL_UDP:
456 protocol = "UDP";
457 break;
458 default:
459 /* error unknown/unsupported type */
460 protocol = "UNKNOWN";
461 break;
464 switch (c->type) {
465 case SIPE_CANDIDATE_TYPE_HOST:
466 type = "host";
467 break;
468 case SIPE_CANDIDATE_TYPE_RELAY:
469 type = "relay";
470 related = g_strdup_printf("raddr %s rport %d ",
471 c->ip,
472 c->port);
473 break;
474 case SIPE_CANDIDATE_TYPE_SRFLX:
475 type = "srflx";
476 related = g_strdup_printf("raddr %s rport %d",
477 c->base_ip,
478 c->base_port);
479 break;
480 case SIPE_CANDIDATE_TYPE_PRFLX:
481 type = "prflx";
482 break;
483 default:
484 /* error unknown/unsupported type */
485 type = "unknown";
486 break;
489 g_string_append_printf(result,
490 "a=candidate:%s %u %s %u %s %d typ %s %s\r\n",
491 c->foundation,
492 c->component,
493 protocol,
494 c->priority,
495 c->ip,
496 c->port,
497 type,
498 related ? related : "");
499 g_free(related);
501 } else if (ice_version == SIPE_ICE_DRAFT_6) {
502 gchar *username;
503 gchar *password;
505 switch (c->protocol) {
506 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
507 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE: {
508 GSList *prev_cand = processed_tcp_candidates;
509 for (; prev_cand; prev_cand = prev_cand->next) {
510 struct sdpcandidate *c2 = (struct sdpcandidate *)prev_cand->data;
512 if (sipe_strequal(c->ip, c2->ip) &&
513 c->component == c2->component) {
514 break;
518 if (prev_cand) {
519 protocol = NULL;
520 } else {
521 protocol = "TCP";
522 processed_tcp_candidates =
523 g_slist_append(processed_tcp_candidates, c);
525 break;
527 case SIPE_NETWORK_PROTOCOL_UDP:
528 protocol = "UDP";
529 break;
530 default:
531 /* unknown/unsupported type, ignore */
532 protocol = NULL;
533 break;
536 if (!protocol) {
537 continue;
540 username = base64_unpad(c->username);
541 password = base64_unpad(c->password);
543 g_string_append_printf(result,
544 "a=candidate:%s %u %s %s 0.%u %s %d\r\n",
545 username,
546 c->component,
547 password,
548 protocol,
549 c->priority,
550 c->ip,
551 c->port);
553 g_free(username);
554 g_free(password);
558 g_slist_free(candidates);
559 g_slist_free(processed_tcp_candidates);
561 return g_string_free(result, FALSE);
564 static gchar *
565 remote_candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
567 GString *result = g_string_new("");
569 candidates = g_slist_copy(candidates);
570 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
572 if (candidates) {
573 if (ice_version == SIPE_ICE_RFC_5245) {
574 GSList *i;
575 g_string_append(result, "a=remote-candidates:");
577 for (i = candidates; i; i = i->next) {
578 struct sdpcandidate *c = i->data;
579 g_string_append_printf(result, "%u %s %u ",
580 c->component, c->ip, c->port);
583 g_string_append(result, "\r\n");
584 } else if (ice_version == SIPE_ICE_DRAFT_6) {
585 struct sdpcandidate *c = candidates->data;
586 g_string_append_printf(result, "a=remote-candidate:%s\r\n",
587 c->username);
591 g_slist_free(candidates);
593 return g_string_free(result, FALSE);
596 static gchar *
597 attributes_to_string(GSList *attributes)
599 GString *result = g_string_new("");
601 for (; attributes; attributes = attributes->next) {
602 struct sipnameval *a = attributes->data;
603 g_string_append_printf(result, "a=%s", a->name);
604 if (!sipe_strequal(a->value, ""))
605 g_string_append_printf(result, ":%s", a->value);
606 g_string_append(result, "\r\n");
609 return g_string_free(result, FALSE);
612 static gchar *
613 media_to_string(const struct sdpmsg *msg, const struct sdpmedia *media)
615 gchar *media_str;
617 gchar *media_conninfo = NULL;
619 gchar *codecs_str = NULL;
620 gchar *codec_ids_str = codec_ids_to_string(media->codecs);
622 gchar *candidates_str = NULL;
623 gchar *remote_candidates_str = NULL;
625 gchar *tcp_setup_str = NULL;
626 gchar *attributes_str = NULL;
627 gchar *credentials = NULL;
629 gboolean uses_tcp_transport = FALSE;
631 if (media->port != 0) {
632 if (!sipe_strequal(msg->ip, media->ip)) {
633 media_conninfo = g_strdup_printf("c=IN IP4 %s\r\n", media->ip);
636 codecs_str = codecs_to_string(media->codecs);
637 candidates_str = candidates_to_string(media->candidates, msg->ice_version);
638 remote_candidates_str = remote_candidates_to_string(media->remote_candidates,
639 msg->ice_version);
641 if (media->remote_candidates) {
642 struct sdpcandidate *c = media->remote_candidates->data;
643 uses_tcp_transport =
644 c->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE ||
645 c->protocol == SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
646 if (uses_tcp_transport) {
647 tcp_setup_str = g_strdup_printf(
648 "a=connection:existing\r\n"
649 "a=setup:%s\r\n",
650 (c->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE) ? "passive" : "active");
654 attributes_str = attributes_to_string(media->attributes);
656 if (msg->ice_version == SIPE_ICE_RFC_5245 && media->candidates) {
657 struct sdpcandidate *c = media->candidates->data;
659 credentials = g_strdup_printf("a=ice-ufrag:%s\r\n"
660 "a=ice-pwd:%s\r\n",
661 c->username,
662 c->password);
666 media_str = g_strdup_printf("m=%s %d %sRTP/AVP%s\r\n"
667 "%s"
668 "%s"
669 "%s"
670 "%s"
671 "%s"
672 "%s"
673 "%s",
674 media->name, media->port, uses_tcp_transport ? "TCP/" : "", codec_ids_str,
675 media_conninfo ? media_conninfo : "",
676 candidates_str ? candidates_str : "",
677 remote_candidates_str ? remote_candidates_str : "",
678 tcp_setup_str ? tcp_setup_str : "",
679 codecs_str ? codecs_str : "",
680 attributes_str ? attributes_str : "",
681 credentials ? credentials : "");
683 g_free(media_conninfo);
684 g_free(codecs_str);
685 g_free(codec_ids_str);
686 g_free(candidates_str);
687 g_free(remote_candidates_str);
688 g_free(tcp_setup_str);
689 g_free(attributes_str);
690 g_free(credentials);
692 return media_str;
695 gchar *
696 sdpmsg_to_string(const struct sdpmsg *msg)
698 GString *body = g_string_new(NULL);
699 GSList *i;
701 g_string_append_printf(
702 body,
703 "v=0\r\n"
704 "o=- 0 0 IN IP4 %s\r\n"
705 "s=session\r\n"
706 "c=IN IP4 %s\r\n"
707 "b=CT:99980\r\n"
708 "t=0 0\r\n",
709 msg->ip, msg->ip);
712 for (i = msg->media; i; i = i->next) {
713 gchar *media_str = media_to_string(msg, i->data);
714 g_string_append(body, media_str);
715 g_free(media_str);
718 return g_string_free(body, FALSE);
721 static struct sdpcandidate *
722 sdpcandidate_copy(struct sdpcandidate *candidate)
724 if (candidate) {
725 struct sdpcandidate *copy = g_new0(struct sdpcandidate, 1);
727 copy->foundation = g_strdup(candidate->foundation);
728 copy->component = candidate->component;
729 copy->type = candidate->type;
730 copy->protocol = candidate->protocol;
731 copy->priority = candidate->priority;
732 copy->ip = g_strdup(candidate->ip);
733 copy->port = candidate->port;
734 copy->base_ip = g_strdup(candidate->base_ip);
735 copy->base_port = candidate->base_port;
736 copy->username = g_strdup(candidate->username);
737 copy->password = g_strdup(candidate->password);
739 return copy;
740 } else
741 return NULL;
744 static void
745 sdpcandidate_free(struct sdpcandidate *candidate)
747 if (candidate) {
748 g_free(candidate->foundation);
749 g_free(candidate->ip);
750 g_free(candidate->base_ip);
751 g_free(candidate->username);
752 g_free(candidate->password);
753 g_free(candidate);
757 static void
758 sdpcodec_free(struct sdpcodec *codec)
760 if (codec) {
761 g_free(codec->name);
762 sipe_utils_nameval_free(codec->parameters);
763 g_free(codec);
767 void
768 sdpmedia_free(struct sdpmedia *media)
770 if (media) {
771 g_free(media->name);
772 g_free(media->ip);
774 sipe_utils_nameval_free(media->attributes);
776 g_slist_free_full(media->candidates,
777 (GDestroyNotify) sdpcandidate_free);
778 g_slist_free_full(media->codecs,
779 (GDestroyNotify) sdpcodec_free);
780 g_slist_free_full(media->remote_candidates,
781 (GDestroyNotify) sdpcandidate_free);
783 g_free(media);
787 void
788 sdpmsg_free(struct sdpmsg *msg)
790 if (msg) {
791 g_free(msg->ip);
792 g_slist_free_full(msg->media,
793 (GDestroyNotify) sdpmedia_free);
794 g_free(msg);
799 Local Variables:
800 mode: c
801 c-file-style: "bsd"
802 indent-tabs-mode: t
803 tab-width: 8
804 End: