sdpmsg: fix a typo UNKOWN -> UNKNOWN
[siplcs.git] / src / core / sdpmsg.c
blob8f38695d7ee179934e2364d7193f129d2947e6f3
1 /**
2 * @file sdpmsg.c
4 * pidgin-sipe
6 * Copyright (C) 2010 Jakub Adam <jakub.adam@ktknet.cz>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #include <glib.h>
29 #include "sipe-backend.h"
30 #include "sdpmsg.h"
31 #include "sipe-utils.h"
33 static gboolean
34 append_attribute(struct sdpmedia *media, gchar *attr)
36 gchar **parts = g_strsplit(attr + 2, ":", 2);
38 if(!parts[0]) {
39 g_strfreev(parts);
40 return FALSE;
43 media->attributes = sipe_utils_nameval_add(media->attributes,
44 parts[0],
45 parts[1] ? parts[1] : "");
46 g_strfreev(parts);
47 return TRUE;
50 static gboolean
51 parse_attributes(struct sdpmsg *smsg, gchar *msg) {
52 gchar **lines = g_strsplit(msg, "\r\n", 0);
53 gchar **ptr = lines;
55 while (*ptr != NULL) {
56 if (g_str_has_prefix(*ptr, "o=")) {
57 gchar **parts = g_strsplit(*ptr + 2, " ", 6);
58 smsg->ip = g_strdup(parts[5]);
59 g_strfreev(parts);
60 } else if (g_str_has_prefix(*ptr, "m=")) {
61 gchar **parts = g_strsplit(*ptr + 2, " ", 3);
62 struct sdpmedia *media = g_new0(struct sdpmedia, 1);
64 smsg->media = g_slist_append(smsg->media, media);
66 media->name = g_strdup(parts[0]);
67 media->port = atoi(parts[1]);
69 g_strfreev(parts);
71 while (*(++ptr) && !g_str_has_prefix(*ptr, "m=")) {
73 if (g_str_has_prefix(*ptr, "a=")) {
74 if (!append_attribute(media, *ptr)) {
75 g_strfreev(lines);
76 return FALSE;
80 continue;
83 ++ptr;
86 g_strfreev(lines);
88 return TRUE;
91 static struct sdpcandidate * sdpcandidate_copy(struct sdpcandidate *candidate);
92 static void sdpcandidate_free(struct sdpcandidate *candidate);
94 static SipeComponentType
95 parse_component(const gchar *str)
97 switch (atoi(str)) {
98 case 1: return SIPE_COMPONENT_RTP;
99 case 2: return SIPE_COMPONENT_RTCP;
100 default: return SIPE_COMPONENT_NONE;
104 static gchar *
105 base64_pad(const gchar* str)
107 size_t str_len = strlen(str);
108 int mod = str_len % 4;
110 if (mod > 0) {
111 gchar *result = NULL;
112 int pad = 4 - mod;
113 gchar *ptr = result = g_malloc(str_len + pad + 1);
115 memcpy(ptr, str, str_len);
116 ptr += str_len;
117 memset(ptr, '=', pad);
118 ptr += pad;
119 *ptr = '\0';
121 return result;
122 } else
123 return g_strdup(str);
126 static GSList *
127 parse_append_candidate_draft_6(gchar **tokens, GSList *candidates)
129 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
131 candidate->username = base64_pad(tokens[0]);
132 candidate->component = parse_component(tokens[1]);
133 candidate->password = base64_pad(tokens[2]);
135 if (sipe_strequal(tokens[3], "UDP"))
136 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
137 else if (sipe_strequal(tokens[3], "TCP"))
138 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
139 else {
140 sdpcandidate_free(candidate);
141 return candidates;
144 candidate->priority = atoi(tokens[4] + 2);
145 candidate->ip = g_strdup(tokens[5]);
146 candidate->port = atoi(tokens[6]);
148 candidates = g_slist_append(candidates, candidate);
150 // draft 6 candidates are both active and passive
151 if (candidate->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE) {
152 candidate = sdpcandidate_copy(candidate);
153 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
154 candidates = g_slist_append(candidates, candidate);
157 return candidates;
160 static GSList *
161 parse_append_candidate_rfc_5245(gchar **tokens, GSList *candidates)
163 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
165 candidate->foundation = g_strdup(tokens[0]);
166 candidate->component = parse_component(tokens[1]);
168 if (sipe_strequal(tokens[2], "UDP"))
169 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
170 else if (sipe_strequal(tokens[2], "TCP-ACT"))
171 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
172 else if (sipe_strequal(tokens[2], "TCP-PASS"))
173 candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
174 else {
175 sdpcandidate_free(candidate);
176 return candidates;
179 candidate->priority = atoi(tokens[3]);
180 candidate->ip = g_strdup(tokens[4]);
181 candidate->port = atoi(tokens[5]);
183 if (sipe_strequal(tokens[7], "host"))
184 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
185 else if (sipe_strequal(tokens[7], "relay"))
186 candidate->type = SIPE_CANDIDATE_TYPE_RELAY;
187 else if (sipe_strequal(tokens[7], "srflx"))
188 candidate->type = SIPE_CANDIDATE_TYPE_SRFLX;
189 else if (sipe_strequal(tokens[7], "prflx"))
190 candidate->type = SIPE_CANDIDATE_TYPE_PRFLX;
191 else {
192 sdpcandidate_free(candidate);
193 return candidates;
196 return g_slist_append(candidates, candidate);
199 static GSList *
200 parse_candidates(GSList *attrs, SipeIceVersion *ice_version)
202 GSList *candidates = NULL;
203 const gchar *attr;
204 int i = 0;
206 while ((attr = sipe_utils_nameval_find_instance(attrs, "candidate", i++))) {
207 gchar **tokens = g_strsplit_set(attr, " ", 0);
209 if (sipe_strequal(tokens[6], "typ")) {
210 candidates = parse_append_candidate_rfc_5245(tokens, candidates);
211 if (candidates)
212 *ice_version = SIPE_ICE_RFC_5245;
213 } else {
214 candidates = parse_append_candidate_draft_6(tokens, candidates);
215 if (candidates)
216 *ice_version = SIPE_ICE_DRAFT_6;
219 g_strfreev(tokens);
222 if (!candidates)
223 *ice_version = SIPE_ICE_NO_ICE;
225 if (*ice_version == SIPE_ICE_RFC_5245) {
226 const gchar *username = sipe_utils_nameval_find(attrs, "ice-ufrag");
227 const gchar *password = sipe_utils_nameval_find(attrs, "ice-pwd");
229 if (username && password) {
230 GSList *i;
231 for (i = candidates; i; i = i->next) {
232 struct sdpcandidate *c = i->data;
233 c->username = g_strdup(username);
234 c->password = g_strdup(password);
239 return candidates;
242 static GSList *
243 create_legacy_candidates(gchar *ip, guint16 port)
245 struct sdpcandidate *candidate;
246 GSList *candidates = NULL;
248 candidate = g_new0(struct sdpcandidate, 1);
249 candidate->foundation = g_strdup("1");
250 candidate->component = SIPE_COMPONENT_RTP;
251 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
252 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
253 candidate->ip = g_strdup(ip);
254 candidate->port = port;
256 candidates = g_slist_append(candidates, candidate);
258 candidate = g_new0(struct sdpcandidate, 1);
259 candidate->foundation = g_strdup("1");
260 candidate->component = SIPE_COMPONENT_RTCP;
261 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
262 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
263 candidate->ip = g_strdup(ip);
264 candidate->port = port + 1;
266 candidates = g_slist_append(candidates, candidate);
268 return candidates;
271 static GSList *
272 parse_codecs(GSList *attrs, SipeMediaType type)
274 int i = 0;
275 const gchar *attr;
276 GSList *codecs = NULL;
278 while ((attr = sipe_utils_nameval_find_instance(attrs, "rtpmap", i++))) {
279 struct sdpcodec *codec = g_new0(struct sdpcodec, 1);
280 gchar **tokens = g_strsplit_set(attr, " /", 3);
282 int j = 0;
283 const gchar* params;
285 codec->id = atoi(tokens[0]);
286 codec->name = g_strdup(tokens[1]);
287 codec->clock_rate = atoi(tokens[2]);
288 codec->type = type;
290 // TODO: more secure and effective implementation
291 while((params = sipe_utils_nameval_find_instance(attrs, "fmtp", j++))) {
292 gchar **tokens = g_strsplit_set(params, " ", 0);
293 gchar **next = tokens + 1;
295 if (atoi(tokens[0]) == codec->id) {
296 while (*next) {
297 gchar name[50];
298 gchar value[50];
300 if (sscanf(*next, "%[a-zA-Z0-9]=%s", name, value) == 2)
301 codec->parameters = sipe_utils_nameval_add(codec->parameters, name, value);
303 ++next;
307 g_strfreev(tokens);
310 codecs = g_slist_append(codecs, codec);
311 g_strfreev(tokens);
314 return codecs;
317 struct sdpmsg *
318 sdpmsg_parse_msg(gchar *msg)
320 struct sdpmsg *smsg = g_new0(struct sdpmsg, 1);
321 GSList *i;
323 if (!parse_attributes(smsg, msg)) {
324 sdpmsg_free(smsg);
325 return NULL;
328 for (i = smsg->media; i; i = i->next) {
329 struct sdpmedia *media = i->data;
330 SipeMediaType type;
332 media->candidates = parse_candidates(media->attributes,
333 &smsg->ice_version);
335 if (!media->candidates && media->port != 0) {
336 // No a=candidate in SDP message, this seems to be MSOC 2005
337 media->candidates = create_legacy_candidates(smsg->ip, media->port);
340 if (sipe_strequal(media->name, "audio"))
341 type = SIPE_MEDIA_AUDIO;
342 else if (sipe_strequal(media->name, "video"))
343 type = SIPE_MEDIA_VIDEO;
344 else {
345 // Unknown media type
346 sdpmsg_free(smsg);
347 return NULL;
350 media->codecs = parse_codecs(media->attributes, type);
353 return smsg;
356 static gchar *
357 codecs_to_string(GSList *codecs)
359 GString *result = g_string_new(NULL);
361 for (; codecs; codecs = codecs->next) {
362 struct sdpcodec *c = codecs->data;
363 GSList *params = c->parameters;
365 g_string_append_printf(result,
366 "a=rtpmap:%d %s/%d\r\n",
367 c->id,
368 c->name,
369 c->clock_rate);
371 if (params) {
372 g_string_append_printf(result, "a=fmtp:%d", c->id);
374 for (; params; params = params->next) {
375 struct sipnameval* par = params->data;
376 g_string_append_printf(result, " %s=%s",
377 par->name, par->value);
380 g_string_append(result, "\r\n");
384 return g_string_free(result, FALSE);
387 static gchar *
388 codec_ids_to_string(GSList *codecs)
390 GString *result = g_string_new(NULL);
392 for (; codecs; codecs = codecs->next) {
393 struct sdpcodec *c = codecs->data;
394 g_string_append_printf(result, " %d", c->id);
397 return g_string_free(result, FALSE);
400 static gchar *
401 base64_unpad(const gchar *str)
403 gchar *result = g_strdup(str);
404 gchar *ptr;
406 for (ptr = result + strlen(result); ptr != result; --ptr) {
407 if (*(ptr - 1) != '=') {
408 *ptr = '\0';
409 break;
413 return result;
416 static gint
417 candidate_sort_cb(struct sdpcandidate *c1, struct sdpcandidate *c2)
419 int cmp = sipe_strcompare(c1->foundation, c2->foundation);
420 if (cmp == 0) {
421 cmp = sipe_strcompare(c1->username, c2->username);
422 if (cmp == 0)
423 cmp = c1->component - c2->component;
426 return cmp;
429 static gchar *
430 candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
432 GString *result = g_string_new("");
433 GSList *i;
435 candidates = g_slist_copy(candidates);
436 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
438 for (i = candidates; i; i = i->next) {
439 struct sdpcandidate *c = i->data;
440 const gchar *protocol;
441 const gchar *type;
442 gchar *related = NULL;
444 if (ice_version == SIPE_ICE_RFC_5245) {
446 switch (c->protocol) {
447 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
448 protocol = "TCP-ACT";
449 break;
450 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
451 protocol = "TCP-PASS";
452 break;
453 case SIPE_NETWORK_PROTOCOL_UDP:
454 protocol = "UDP";
455 break;
456 default:
457 /* error unknown/unsupported type */
458 protocol = "UNKNOWN";
459 break;
462 switch (c->type) {
463 case SIPE_CANDIDATE_TYPE_HOST:
464 type = "host";
465 break;
466 case SIPE_CANDIDATE_TYPE_RELAY:
467 type = "relay";
468 related = g_strdup_printf("raddr %s rport %d ",
469 c->ip,
470 c->port);
471 break;
472 case SIPE_CANDIDATE_TYPE_SRFLX:
473 type = "srflx";
474 related = g_strdup_printf("raddr %s rport %d",
475 c->base_ip,
476 c->base_port);
477 break;
478 case SIPE_CANDIDATE_TYPE_PRFLX:
479 type = "prflx";
480 break;
481 default:
482 /* error unknown/unsupported type */
483 type = "unknown";
484 break;
487 g_string_append_printf(result,
488 "a=candidate:%s %u %s %u %s %d typ %s %s\r\n",
489 c->foundation,
490 c->component,
491 protocol,
492 c->priority,
493 c->ip,
494 c->port,
495 type,
496 related ? related : "");
497 g_free(related);
499 } else if (ice_version == SIPE_ICE_DRAFT_6) {
500 gchar *username = base64_unpad(c->username);
501 gchar *password = base64_unpad(c->password);
503 // TODO: remove active/passive pairs for the same host IP
504 switch (c->protocol) {
505 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
506 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
507 protocol = "TCP";
508 break;
509 case SIPE_NETWORK_PROTOCOL_UDP:
510 protocol = "UDP";
511 break;
512 default:
513 /* error unknown/unsupported type */
514 protocol = "UNKNOWN";
515 break;
518 g_string_append_printf(result,
519 "a=candidate:%s %u %s %s 0.%u %s %d\r\n",
520 username,
521 c->component,
522 password,
523 protocol,
524 c->priority,
525 c->ip,
526 c->port);
528 g_free(username);
529 g_free(password);
533 g_slist_free(candidates);
535 return g_string_free(result, FALSE);
538 static gchar *
539 remote_candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
541 GString *result = g_string_new("");
543 candidates = g_slist_copy(candidates);
544 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
546 if (candidates) {
547 if (ice_version == SIPE_ICE_RFC_5245) {
548 GSList *i;
549 g_string_append(result, "a=remote-candidates:");
551 for (i = candidates; i; i = i->next) {
552 struct sdpcandidate *c = i->data;
553 g_string_append_printf(result, "%u %s %u ",
554 c->component, c->ip, c->port);
557 g_string_append(result, "\r\n");
558 } else if (ice_version == SIPE_ICE_DRAFT_6) {
559 struct sdpcandidate *c = candidates->data;
560 g_string_append_printf(result, "a=remote-candidate:%s\r\n",
561 c->username);
565 g_slist_free(candidates);
567 return g_string_free(result, FALSE);
570 static gchar *
571 attributes_to_string(GSList *attributes)
573 GString *result = g_string_new("");
575 for (; attributes; attributes = attributes->next) {
576 struct sipnameval *a = attributes->data;
577 g_string_append_printf(result, "a=%s", a->name);
578 if (!sipe_strequal(a->value, ""))
579 g_string_append_printf(result, ":%s", a->value);
580 g_string_append(result, "\r\n");
583 return g_string_free(result, FALSE);
586 static gchar *
587 media_to_string(const struct sdpmsg *msg, const struct sdpmedia *media)
589 gchar *media_str;
591 gchar *media_conninfo = NULL;
593 gchar *codecs_str = NULL;
594 gchar *codec_ids_str = codec_ids_to_string(media->codecs);
596 gchar *candidates_str = NULL;
597 gchar *remote_candidates_str = NULL;
599 gchar *attributes_str = NULL;
600 gchar *credentials = NULL;
602 gboolean uses_tcp_transport = FALSE;
604 if (media->port != 0) {
605 if (!sipe_strequal(msg->ip, media->ip)) {
606 media_conninfo = g_strdup_printf("c=IN IP4 %s\r\n", media->ip);
609 codecs_str = codecs_to_string(media->codecs);
610 candidates_str = candidates_to_string(media->candidates, msg->ice_version);
611 remote_candidates_str = remote_candidates_to_string(media->remote_candidates,
612 msg->ice_version);
614 if (media->remote_candidates) {
615 struct sdpcandidate *c = media->remote_candidates->data;
616 uses_tcp_transport =
617 c->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE ||
618 c->protocol == SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
621 attributes_str = attributes_to_string(media->attributes);
622 credentials = NULL;
625 if (msg->ice_version == SIPE_ICE_RFC_5245 && media->candidates) {
626 struct sdpcandidate *c = media->candidates->data;
628 credentials = g_strdup_printf("a=ice-ufrag:%s\r\n"
629 "a=ice-pwd:%s\r\n",
630 c->username,
631 c->password);
635 media_str = g_strdup_printf("m=%s %d %sRTP/AVP%s\r\n"
636 "%s"
637 "%s"
638 "%s"
639 "%s"
640 "%s"
641 "%s",
642 media->name, media->port, uses_tcp_transport ? "TCP/" : "", codec_ids_str,
643 media_conninfo ? media_conninfo : "",
644 candidates_str ? candidates_str : "",
645 remote_candidates_str ? remote_candidates_str : "",
646 codecs_str ? codecs_str : "",
647 attributes_str ? attributes_str : "",
648 credentials ? credentials : "");
650 g_free(media_conninfo);
651 g_free(codecs_str);
652 g_free(codec_ids_str);
653 g_free(candidates_str);
654 g_free(remote_candidates_str);
655 g_free(attributes_str);
656 g_free(credentials);
658 return media_str;
661 gchar *
662 sdpmsg_to_string(const struct sdpmsg *msg)
664 GString *body = g_string_new(NULL);
665 GSList *i;
667 g_string_append_printf(
668 body,
669 "v=0\r\n"
670 "o=- 0 0 IN IP4 %s\r\n"
671 "s=session\r\n"
672 "c=IN IP4 %s\r\n"
673 "b=CT:99980\r\n"
674 "t=0 0\r\n",
675 msg->ip, msg->ip);
678 for (i = msg->media; i; i = i->next) {
679 gchar *media_str = media_to_string(msg, i->data);
680 g_string_append(body, media_str);
681 g_free(media_str);
684 return g_string_free(body, FALSE);
687 static struct sdpcandidate *
688 sdpcandidate_copy(struct sdpcandidate *candidate)
690 if (candidate) {
691 struct sdpcandidate *copy = g_new0(struct sdpcandidate, 1);
693 copy->foundation = g_strdup(candidate->foundation);
694 copy->component = candidate->component;
695 copy->type = candidate->type;
696 copy->protocol = candidate->protocol;
697 copy->priority = candidate->priority;
698 copy->ip = g_strdup(candidate->ip);
699 copy->port = candidate->port;
700 copy->base_ip = g_strdup(candidate->base_ip);
701 copy->base_port = candidate->base_port;
702 copy->username = g_strdup(candidate->username);
703 copy->password = g_strdup(candidate->password);
705 return copy;
706 } else
707 return NULL;
710 static void
711 sdpcandidate_free(struct sdpcandidate *candidate)
713 if (candidate) {
714 g_free(candidate->foundation);
715 g_free(candidate->ip);
716 g_free(candidate->base_ip);
717 g_free(candidate->username);
718 g_free(candidate->password);
719 g_free(candidate);
723 static void
724 sdpcodec_free(struct sdpcodec *codec)
726 if (codec) {
727 g_free(codec->name);
728 sipe_utils_nameval_free(codec->parameters);
729 g_free(codec);
733 void
734 sdpmedia_free(struct sdpmedia *media)
736 if (media) {
737 GSList *item;
739 g_free(media->name);
740 g_free(media->ip);
742 sipe_utils_nameval_free(media->attributes);
744 for (item = media->candidates; item; item = item->next)
745 sdpcandidate_free(item->data);
746 g_slist_free(media->candidates);
748 for (item = media->codecs; item; item = item->next)
749 sdpcodec_free(item->data);
750 g_slist_free(media->codecs);
752 for (item = media->remote_candidates; item; item = item->next)
753 sdpcandidate_free(item->data);
754 g_slist_free(media->remote_candidates);
756 g_free(media);
760 void
761 sdpmsg_free(struct sdpmsg *msg)
763 if (msg) {
764 GSList *item;
766 g_free(msg->ip);
767 for (item = msg->media; item; item = item->next)
768 sdpmedia_free(item->data);
769 g_slist_free(msg->media);
770 g_free(msg);
775 Local Variables:
776 mode: c
777 c-file-style: "bsd"
778 indent-tabs-mode: t
779 tab-width: 8
780 End: