Fix sipe_buddy_menu_copy_to_cb when copying buddy not yet in our list
[siplcs.git] / src / core / sdpmsg.c
blob64c26ca0c3736dcb2c91061b58e4f177a32ae69f
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 void sdpcandidate_free(struct sdpcandidate *candidate);
93 static SipeComponentType
94 parse_component(const gchar *str)
96 switch (atoi(str)) {
97 case 1: return SIPE_COMPONENT_RTP;
98 case 2: return SIPE_COMPONENT_RTCP;
99 default: return SIPE_COMPONENT_NONE;
103 static gchar *
104 base64_pad(const gchar* str)
106 size_t str_len = strlen(str);
107 int mod = str_len % 4;
109 if (mod > 0) {
110 gchar *result = NULL;
111 int pad = 4 - mod;
112 gchar *ptr = result = g_malloc(str_len + pad + 1);
114 memcpy(ptr, str, str_len);
115 ptr += str_len;
116 memset(ptr, '=', pad);
117 ptr += pad;
118 *ptr = '\0';
120 return result;
121 } else
122 return g_strdup(str);
125 static struct sdpcandidate *
126 parse_candidate_draft_6(gchar **tokens)
128 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
130 candidate->username = base64_pad(tokens[0]);
131 candidate->component = parse_component(tokens[1]);
132 candidate->password = base64_pad(tokens[2]);
134 if (sipe_strequal(tokens[3], "UDP"))
135 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
136 else {
137 // Ignore TCP candidates, at least for now...
138 // Also, if this is ICEv6 candidate list, candidates are dropped here
139 sdpcandidate_free(candidate);
140 return NULL;
143 candidate->priority = atoi(tokens[4] + 2);
144 candidate->ip = g_strdup(tokens[5]);
145 candidate->port = atoi(tokens[6]);
147 return candidate;
150 static struct sdpcandidate *
151 parse_candidate_rfc_5245(gchar **tokens)
153 struct sdpcandidate *candidate = g_new0(struct sdpcandidate, 1);
155 candidate->foundation = g_strdup(tokens[0]);
156 candidate->component = parse_component(tokens[1]);
158 if (sipe_strequal(tokens[2], "UDP"))
159 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
160 else {
161 // Ignore TCP candidates, at least for now...
162 // Also, if this is ICEv6 candidate list, candidates are dropped here
163 sdpcandidate_free(candidate);
164 return NULL;
167 candidate->priority = atoi(tokens[3]);
168 candidate->ip = g_strdup(tokens[4]);
169 candidate->port = atoi(tokens[5]);
171 if (sipe_strequal(tokens[7], "host"))
172 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
173 else if (sipe_strequal(tokens[7], "relay"))
174 candidate->type = SIPE_CANDIDATE_TYPE_RELAY;
175 else if (sipe_strequal(tokens[7], "srflx"))
176 candidate->type = SIPE_CANDIDATE_TYPE_SRFLX;
177 else if (sipe_strequal(tokens[7], "prflx"))
178 candidate->type = SIPE_CANDIDATE_TYPE_PRFLX;
179 else {
180 sdpcandidate_free(candidate);
181 return NULL;
184 return candidate;
187 static GSList *
188 parse_candidates(GSList *attrs, SipeIceVersion *ice_version)
190 GSList *candidates = NULL;
191 const gchar *attr;
192 int i = 0;
194 while ((attr = sipe_utils_nameval_find_instance(attrs, "candidate", i++))) {
195 gchar **tokens = g_strsplit_set(attr, " ", 0);
197 if (sipe_strequal(tokens[6], "typ")) {
198 struct sdpcandidate *c = parse_candidate_rfc_5245(tokens);
200 if (c) {
201 *ice_version = SIPE_ICE_RFC_5245;
202 candidates = g_slist_append(candidates, c);
204 } else {
205 struct sdpcandidate *c = parse_candidate_draft_6(tokens);
206 if (c) {
207 *ice_version = SIPE_ICE_DRAFT_6;
208 candidates = g_slist_append(candidates, c);
212 g_strfreev(tokens);
215 if (!candidates)
216 *ice_version = SIPE_ICE_NO_ICE;
218 if (*ice_version == SIPE_ICE_RFC_5245) {
219 const gchar *username = sipe_utils_nameval_find(attrs, "ice-ufrag");
220 const gchar *password = sipe_utils_nameval_find(attrs, "ice-pwd");
222 if (username && password) {
223 GSList *i;
224 for (i = candidates; i; i = i->next) {
225 struct sdpcandidate *c = i->data;
226 c->username = g_strdup(username);
227 c->password = g_strdup(password);
232 return candidates;
235 static GSList *
236 create_legacy_candidates(gchar *ip, guint16 port)
238 struct sdpcandidate *candidate;
239 GSList *candidates = NULL;
241 candidate = g_new0(struct sdpcandidate, 1);
242 candidate->foundation = g_strdup("1");
243 candidate->component = SIPE_COMPONENT_RTP;
244 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
245 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
246 candidate->ip = g_strdup(ip);
247 candidate->port = port;
249 candidates = g_slist_append(candidates, candidate);
251 candidate = g_new0(struct sdpcandidate, 1);
252 candidate->foundation = g_strdup("1");
253 candidate->component = SIPE_COMPONENT_RTCP;
254 candidate->type = SIPE_CANDIDATE_TYPE_HOST;
255 candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
256 candidate->ip = g_strdup(ip);
257 candidate->port = port + 1;
259 candidates = g_slist_append(candidates, candidate);
261 return candidates;
264 static GSList *
265 parse_codecs(GSList *attrs, SipeMediaType type)
267 int i = 0;
268 const gchar *attr;
269 GSList *codecs = NULL;
271 while ((attr = sipe_utils_nameval_find_instance(attrs, "rtpmap", i++))) {
272 struct sdpcodec *codec = g_new0(struct sdpcodec, 1);
273 gchar **tokens = g_strsplit_set(attr, " /", 3);
275 int j = 0;
276 const gchar* params;
278 codec->id = atoi(tokens[0]);
279 codec->name = g_strdup(tokens[1]);
280 codec->clock_rate = atoi(tokens[2]);
281 codec->type = type;
283 // TODO: more secure and effective implementation
284 while((params = sipe_utils_nameval_find_instance(attrs, "fmtp", j++))) {
285 gchar **tokens = g_strsplit_set(params, " ", 0);
286 gchar **next = tokens + 1;
288 if (atoi(tokens[0]) == codec->id) {
289 while (*next) {
290 gchar name[50];
291 gchar value[50];
293 if (sscanf(*next, "%[a-zA-Z0-9]=%s", name, value) == 2)
294 codec->parameters = sipe_utils_nameval_add(codec->parameters, name, value);
296 ++next;
300 g_strfreev(tokens);
303 codecs = g_slist_append(codecs, codec);
304 g_strfreev(tokens);
307 return codecs;
310 struct sdpmsg *
311 sdpmsg_parse_msg(gchar *msg)
313 struct sdpmsg *smsg = g_new0(struct sdpmsg, 1);
314 GSList *i;
316 if (!parse_attributes(smsg, msg)) {
317 sdpmsg_free(smsg);
318 return NULL;
321 for (i = smsg->media; i; i = i->next) {
322 struct sdpmedia *media = i->data;
323 SipeMediaType type;
325 media->candidates = parse_candidates(media->attributes,
326 &smsg->ice_version);
328 if (!media->candidates && media->port != 0) {
329 // No a=candidate in SDP message, this seems to be MSOC 2005
330 media->candidates = create_legacy_candidates(smsg->ip, media->port);
333 if (sipe_strequal(media->name, "audio"))
334 type = SIPE_MEDIA_AUDIO;
335 else if (sipe_strequal(media->name, "video"))
336 type = SIPE_MEDIA_VIDEO;
337 else {
338 // Unknown media type
339 sdpmsg_free(smsg);
340 return NULL;
343 media->codecs = parse_codecs(media->attributes, type);
346 return smsg;
349 static gchar *
350 codecs_to_string(GSList *codecs)
352 GString *result = g_string_new(NULL);
354 for (; codecs; codecs = codecs->next) {
355 struct sdpcodec *c = codecs->data;
356 GSList *params = c->parameters;
358 g_string_append_printf(result,
359 "a=rtpmap:%d %s/%d\r\n",
360 c->id,
361 c->name,
362 c->clock_rate);
364 if (params) {
365 g_string_append_printf(result, "a=fmtp:%d", c->id);
367 for (; params; params = params->next) {
368 struct sipnameval* par = params->data;
369 g_string_append_printf(result, " %s=%s",
370 par->name, par->value);
373 g_string_append(result, "\r\n");
377 return g_string_free(result, FALSE);
380 static gchar *
381 codec_ids_to_string(GSList *codecs)
383 GString *result = g_string_new(NULL);
385 for (; codecs; codecs = codecs->next) {
386 struct sdpcodec *c = codecs->data;
387 g_string_append_printf(result, " %d", c->id);
390 return g_string_free(result, FALSE);
393 static gchar *
394 base64_unpad(const gchar *str)
396 gchar *result = g_strdup(str);
397 gchar *ptr;
399 for (ptr = result + strlen(result); ptr != result; --ptr) {
400 if (*(ptr - 1) != '=') {
401 *ptr = '\0';
402 break;
406 return result;
409 static gint
410 candidate_sort_cb(struct sdpcandidate *c1, struct sdpcandidate *c2)
412 int cmp = sipe_strcompare(c1->foundation, c2->foundation);
413 if (cmp == 0) {
414 cmp = sipe_strcompare(c1->username, c2->username);
415 if (cmp == 0)
416 cmp = c1->component - c2->component;
419 return cmp;
422 static gchar *
423 candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
425 GString *result = g_string_new("");
426 GSList *i;
428 candidates = g_slist_copy(candidates);
429 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
431 for (i = candidates; i; i = i->next) {
432 struct sdpcandidate *c = i->data;
433 const gchar *protocol;
434 const gchar *type;
435 gchar *related = NULL;
437 switch (c->protocol) {
438 case SIPE_NETWORK_PROTOCOL_TCP:
439 protocol = "TCP";
440 break;
441 case SIPE_NETWORK_PROTOCOL_UDP:
442 protocol = "UDP";
443 break;
446 if (ice_version == SIPE_ICE_RFC_5245) {
448 switch (c->type) {
449 case SIPE_CANDIDATE_TYPE_HOST:
450 type = "host";
451 break;
452 case SIPE_CANDIDATE_TYPE_RELAY:
453 type = "relay";
454 related = g_strdup_printf("raddr %s rport %d ",
455 c->ip,
456 c->port);
457 break;
458 case SIPE_CANDIDATE_TYPE_SRFLX:
459 type = "srflx";
460 related = g_strdup_printf("raddr %s rport %d",
461 c->base_ip,
462 c->base_port);
463 break;
464 case SIPE_CANDIDATE_TYPE_PRFLX:
465 type = "prflx";
466 break;
467 default:
468 // TODO: error unknown/unsupported type
469 break;
472 g_string_append_printf(result,
473 "a=candidate:%s %u %s %u %s %d typ %s %s\r\n",
474 c->foundation,
475 c->component,
476 protocol,
477 c->priority,
478 c->ip,
479 c->port,
480 type,
481 related ? related : "");
482 g_free(related);
484 } else if (ice_version == SIPE_ICE_DRAFT_6) {
485 gchar *username = base64_unpad(c->username);
486 gchar *password = base64_unpad(c->password);
488 g_string_append_printf(result,
489 "a=candidate:%s %u %s %s 0.%u %s %d\r\n",
490 username,
491 c->component,
492 password,
493 protocol,
494 c->priority,
495 c->ip,
496 c->port);
498 g_free(username);
499 g_free(password);
503 g_slist_free(candidates);
505 return g_string_free(result, FALSE);
508 static gchar *
509 remote_candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
511 GString *result = g_string_new("");
513 candidates = g_slist_copy(candidates);
514 candidates = g_slist_sort(candidates, (GCompareFunc)candidate_sort_cb);
516 if (candidates) {
517 if (ice_version == SIPE_ICE_RFC_5245) {
518 GSList *i;
519 g_string_append(result, "a=remote-candidates:");
521 for (i = candidates; i; i = i->next) {
522 struct sdpcandidate *c = i->data;
523 g_string_append_printf(result, "%u %s %u ",
524 c->component, c->ip, c->port);
527 g_string_append(result, "\r\n");
528 } else if (ice_version == SIPE_ICE_DRAFT_6) {
529 struct sdpcandidate *c = candidates->data;
530 g_string_append_printf(result, "a=remote-candidate:%s\r\n",
531 c->username);
535 g_slist_free(candidates);
537 return g_string_free(result, FALSE);
540 static gchar *
541 attributes_to_string(GSList *attributes)
543 GString *result = g_string_new("");
545 for (; attributes; attributes = attributes->next) {
546 struct sipnameval *a = attributes->data;
547 g_string_append_printf(result, "a=%s", a->name);
548 if (!sipe_strequal(a->value, ""))
549 g_string_append_printf(result, ":%s", a->value);
550 g_string_append(result, "\r\n");
553 return g_string_free(result, FALSE);
556 static gchar *
557 media_to_string(const struct sdpmedia *media, SipeIceVersion ice_version)
559 gchar *media_str;
561 gchar *codecs_str = codecs_to_string(media->codecs);
562 gchar *codec_ids_str = codec_ids_to_string(media->codecs);
564 gchar *candidates_str = candidates_to_string(media->candidates, ice_version);
565 gchar *remote_candidates_str = remote_candidates_to_string(media->remote_candidates,
566 ice_version);
568 gchar *attributes_str = attributes_to_string(media->attributes);
569 gchar *credentials = NULL;
571 if (ice_version == SIPE_ICE_RFC_5245 && media->candidates) {
572 struct sdpcandidate *c = media->candidates->data;
574 credentials = g_strdup_printf("a=ice-ufrag:%s\r\n"
575 "a=ice-pwd:%s\r\n",
576 c->username,
577 c->password);
580 media_str = g_strdup_printf("m=%s %d RTP/AVP%s\r\n"
581 "%s"
582 "%s"
583 "%s"
584 "%s"
585 "%s",
586 media->name, media->port, codec_ids_str,
587 candidates_str,
588 remote_candidates_str,
589 codecs_str,
590 attributes_str,
591 credentials ? credentials : "");
593 g_free(codecs_str);
594 g_free(codec_ids_str);
595 g_free(candidates_str);
596 g_free(remote_candidates_str);
597 g_free(attributes_str);
598 g_free(credentials);
600 return media_str;
603 gchar *
604 sdpmsg_to_string(const struct sdpmsg *msg)
606 GString *body = g_string_new(NULL);
607 GSList *i;
609 g_string_append_printf(
610 body,
611 "v=0\r\n"
612 "o=- 0 0 IN IP4 %s\r\n"
613 "s=session\r\n"
614 "c=IN IP4 %s\r\n"
615 "b=CT:99980\r\n"
616 "t=0 0\r\n",
617 msg->ip, msg->ip);
620 for (i = msg->media; i; i = i->next) {
621 gchar *media_str = media_to_string(i->data, msg->ice_version);
622 g_string_append(body, media_str);
623 g_free(media_str);
626 return g_string_free(body, FALSE);
629 static void
630 sdpcandidate_free(struct sdpcandidate *candidate)
632 if (candidate) {
633 g_free(candidate->foundation);
634 g_free(candidate->ip);
635 g_free(candidate->base_ip);
636 g_free(candidate->username);
637 g_free(candidate->password);
638 g_free(candidate);
642 static void
643 sdpcodec_free(struct sdpcodec *codec)
645 if (codec) {
646 g_free(codec->name);
647 sipe_utils_nameval_free(codec->parameters);
648 g_free(codec);
652 static void
653 sdpmedia_free(struct sdpmedia *media)
655 if (media) {
656 GSList *item;
658 g_free(media->name);
659 g_free(media->ip);
661 sipe_utils_nameval_free(media->attributes);
663 for (item = media->candidates; item; item = item->next)
664 sdpcandidate_free(item->data);
665 g_slist_free(media->candidates);
667 for (item = media->codecs; item; item = item->next)
668 sdpcodec_free(item->data);
669 g_slist_free(media->codecs);
671 for (item = media->remote_candidates; item; item = item->next)
672 sdpcandidate_free(item->data);
673 g_slist_free(media->remote_candidates);
675 g_free(media);
679 void
680 sdpmsg_free(struct sdpmsg *msg)
682 if (msg) {
683 GSList *item;
685 g_free(msg->ip);
686 for (item = msg->media; item; item = item->next)
687 sdpmedia_free(item->data);
688 g_slist_free(msg->media);
689 g_free(msg);
694 Local Variables:
695 mode: c
696 c-file-style: "bsd"
697 indent-tabs-mode: t
698 tab-width: 8
699 End: