Blob Blame History Raw
/**
 * @file sdpmsg.c
 *
 * pidgin-sipe
 *
 * Copyright (C) 2013-2017 SIPE Project <http://sipe.sourceforge.net/>
 * Copyright (C) 2010 Jakub Adam <jakub.adam@ktknet.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>

#include "sipe-backend.h"
#include "sipe-core.h"
#include "sdpmsg.h"
#include "sipe-utils.h"

static gboolean
append_attribute(struct sdpmedia *media, gchar *attr)
{
	gchar **parts = g_strsplit(attr + 2, ":", 2);

	if(!parts[0]) {
		g_strfreev(parts);
		return FALSE;
	}

	media->attributes = sipe_utils_nameval_add(media->attributes,
						   parts[0],
						   parts[1] ? parts[1] : "");
	g_strfreev(parts);
	return TRUE;
}

static gboolean
parse_attributes(struct sdpmsg *smsg, const gchar *msg) {
	gchar		**lines = g_strsplit(msg, "\r\n", 0);
	gchar		**ptr = lines;

	while (*ptr != NULL) {
		if (g_str_has_prefix(*ptr, "o=")) {
			gchar **parts = g_strsplit(*ptr + 2, " ", 6);
			if (g_strv_length(parts) != 6) {
				g_strfreev(parts);
				g_strfreev(lines);
				return FALSE;
			}

			smsg->ip = g_strdup(parts[5]);
			g_strfreev(parts);
		} else if (g_str_has_prefix(*ptr, "m=")) {
			gchar **parts;
			struct sdpmedia *media;

			parts = g_strsplit(*ptr + 2, " ", 3);
			if (g_strv_length(parts) < 3) {
				g_strfreev(parts);
				g_strfreev(lines);
				return FALSE;
			}

			media = g_new0(struct sdpmedia, 1);

			smsg->media = g_slist_append(smsg->media, media);

			media->name = g_strdup(parts[0]);
			media->port = atoi(parts[1]);
			media->encryption_active =
					g_strstr_len(parts[2], -1, "/SAVP") != NULL;

			g_strfreev(parts);

			while (*(++ptr) && !g_str_has_prefix(*ptr, "m=")) {

				if (g_str_has_prefix(*ptr, "a=")) {
					if (!append_attribute(media, *ptr)) {
						g_strfreev(lines);
						return FALSE;
					}
				}
			}
			continue;
		}

		++ptr;
	}

	g_strfreev(lines);

	return TRUE;
}

static struct sdpcandidate * sdpcandidate_copy(struct sdpcandidate *candidate);

static SipeComponentType
parse_component(const gchar *str)
{
	switch (atoi(str)) {
		case 1: return  SIPE_COMPONENT_RTP;
		case 2: return  SIPE_COMPONENT_RTCP;
		default: return SIPE_COMPONENT_NONE;
	}
}

static gchar *
base64_pad(const gchar* str)
{
	size_t str_len = strlen(str);
	int mod = str_len % 4;

	if (mod > 0) {
		gchar *result = NULL;
		int pad = 4 - mod;
		gchar *ptr = result = g_malloc(str_len + pad + 1);

		memcpy(ptr, str, str_len);
		ptr += str_len;
		memset(ptr, '=', pad);
		ptr += pad;
		*ptr = '\0';

		return result;
	} else
		return g_strdup(str);
}

static gboolean
parse_append_candidate_draft_6(gchar **tokens, GSList **candidates)
{
	struct sdpcandidate *candidate;

	if (g_strv_length(tokens) < 7 || strlen(tokens[4]) < 3) {
		return FALSE;
	}

	candidate = g_new0(struct sdpcandidate, 1);

	candidate->username = base64_pad(tokens[0]);
	candidate->component = parse_component(tokens[1]);
	candidate->password = base64_pad(tokens[2]);

	if (sipe_strequal(tokens[3], "UDP"))
		candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
	else if (sipe_strequal(tokens[3], "TCP"))
		candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
	else {
		sdpcandidate_free(candidate);
		return FALSE;
	}

	candidate->priority = atoi(tokens[4] + 2);
	candidate->ip = g_strdup(tokens[5]);
	candidate->port = atoi(tokens[6]);

	*candidates = g_slist_append(*candidates, candidate);

	// draft 6 candidates are both active and passive
	if (candidate->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE) {
		candidate = sdpcandidate_copy(candidate);
		candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
		*candidates = g_slist_append(*candidates, candidate);
	}

	return TRUE;
}

static gboolean
parse_append_candidate_rfc_5245(gchar **tokens, GSList **candidates)
{
	struct sdpcandidate *candidate;

	if (g_strv_length(tokens) < 8) {
		return FALSE;
	}

	candidate = g_new0(struct sdpcandidate, 1);
	candidate->foundation = g_strdup(tokens[0]);
	candidate->component = parse_component(tokens[1]);

	if (sipe_strcase_equal(tokens[2], "UDP"))
		candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
	else if (sipe_strcase_equal(tokens[2], "TCP-ACT"))
		candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
	else if (sipe_strcase_equal(tokens[2], "TCP-PASS"))
		candidate->protocol = SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
	else {
		sdpcandidate_free(candidate);
		return FALSE;
	}

	candidate->priority = atoi(tokens[3]);
	candidate->ip = g_strdup(tokens[4]);
	candidate->port = atoi(tokens[5]);

	if (sipe_strcase_equal(tokens[7], "host"))
		candidate->type = SIPE_CANDIDATE_TYPE_HOST;
	else if (sipe_strcase_equal(tokens[7], "relay"))
		candidate->type = SIPE_CANDIDATE_TYPE_RELAY;
	else if (sipe_strcase_equal(tokens[7], "srflx"))
		candidate->type = SIPE_CANDIDATE_TYPE_SRFLX;
	else if (sipe_strcase_equal(tokens[7], "prflx"))
		candidate->type = SIPE_CANDIDATE_TYPE_PRFLX;
	else {
		sdpcandidate_free(candidate);
		return FALSE;
	}

	*candidates = g_slist_append(*candidates, candidate);

	return TRUE;
}

static gboolean
parse_candidates(GSList *attrs, SipeIceVersion *ice_version, GSList **candidates)
{
	const gchar *attr;
	int i = 0;

	g_return_val_if_fail(*candidates == NULL, FALSE);

	*ice_version = SIPE_ICE_NO_ICE;

	while ((attr = sipe_utils_nameval_find_instance(attrs, "candidate", i++))) {
		gchar **tokens = g_strsplit_set(attr, " ", 0);
		gboolean parsed_ok;

		if (g_strv_length(tokens) < 7) {
			g_strfreev(tokens);
			return FALSE;
		}

		if (sipe_strequal(tokens[6], "typ")) {
			parsed_ok = parse_append_candidate_rfc_5245(tokens,
								    candidates);
			if (*candidates)
				*ice_version = SIPE_ICE_RFC_5245;
		} else {
			parsed_ok = parse_append_candidate_draft_6(tokens,
								   candidates);
			if (*candidates)
				*ice_version = SIPE_ICE_DRAFT_6;
		}

		g_strfreev(tokens);

		if (!parsed_ok) {
			return FALSE;
		}
	}

	if (*ice_version == SIPE_ICE_RFC_5245) {
		const gchar *username = sipe_utils_nameval_find(attrs, "ice-ufrag");
		const gchar *password = sipe_utils_nameval_find(attrs, "ice-pwd");

		if (username && password) {
			GSList *i;
			for (i = *candidates; i; i = i->next) {
				struct sdpcandidate *c = i->data;
				c->username = g_strdup(username);
				c->password = g_strdup(password);
			}
		}
	}

	return TRUE;
}

static GSList *
create_legacy_candidates(gchar *ip, guint16 port)
{
	struct sdpcandidate *candidate;
	GSList *candidates = NULL;

	candidate = g_new0(struct sdpcandidate, 1);
	candidate->foundation = g_strdup("1");
	candidate->component = SIPE_COMPONENT_RTP;
	candidate->type = SIPE_CANDIDATE_TYPE_HOST;
	candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
	candidate->ip = g_strdup(ip);
	candidate->port = port;

	candidates = g_slist_append(candidates, candidate);

	candidate = g_new0(struct sdpcandidate, 1);
	candidate->foundation = g_strdup("1");
	candidate->component = SIPE_COMPONENT_RTCP;
	candidate->type = SIPE_CANDIDATE_TYPE_HOST;
	candidate->protocol = SIPE_NETWORK_PROTOCOL_UDP;
	candidate->ip = g_strdup(ip);
	candidate->port = port + 1;

	candidates = g_slist_append(candidates, candidate);

	return candidates;
}

static gboolean
parse_codec_parameters(GSList *attrs, struct sdpcodec *codec)
{
	const gchar* params;
	int i = 0;

	while((params = sipe_utils_nameval_find_instance(attrs, "fmtp", i++))) {
		gchar **tokens;
		gchar **param;

		tokens = g_strsplit(params, " ", 0);
		if (g_strv_length(tokens) < 1) {
			g_strfreev(tokens);
			return FALSE;
		}

		if (atoi(tokens[0]) != codec->id) {
			g_strfreev(tokens);
			continue;
		}

		for (param = tokens + 1; *param; ++param) {
			gchar **nameval = g_strsplit(*param, "=", 2);

			if (g_strv_length(nameval) != 2) {
				g_strfreev(nameval);
				continue;
			}

			codec->parameters =
					sipe_utils_nameval_add(codec->parameters,
							       nameval[0],
							       nameval[1]);

			g_strfreev(nameval);
		}

		g_strfreev(tokens);
	}

	return TRUE;
}


static gboolean
parse_codecs(GSList *attrs, SipeMediaType type, GSList **codecs)
{
	int i = 0;
	const gchar *attr;

	while ((attr = sipe_utils_nameval_find_instance(attrs, "rtpmap", i++))) {
		struct sdpcodec *codec;
		gchar **tokens;

		tokens = g_strsplit_set(attr, " /", 4);
		if (g_strv_length(tokens) < 3) {
			g_strfreev(tokens);
			return FALSE;
		}

		codec = g_new0(struct sdpcodec, 1);
		codec->id = atoi(tokens[0]);
		codec->name = g_strdup(tokens[1]);
		codec->clock_rate = atoi(tokens[2]);
		codec->type = type;

		if (type == SIPE_MEDIA_AUDIO) {
			codec->channels = tokens[3] ? atoi(tokens[3]) : 1;
		}

		g_strfreev(tokens);

		if (!parse_codec_parameters(attrs, codec)) {
			sdpcodec_free(codec);
			return FALSE;
		}

		*codecs = g_slist_append(*codecs, codec);
	}

	return TRUE;
}

static void
parse_encryption_key(GSList *attrs, guchar **key, int *key_id)
{
	int i = 0;
	const gchar *attr;

	while ((attr = sipe_utils_nameval_find_instance(attrs, "crypto", i++))) {
		gchar **tokens = g_strsplit_set(attr, " :|", 6);

		if (tokens[0] && tokens[1] && tokens[2] && tokens[3] && tokens[4] &&
		    sipe_strcase_equal(tokens[1], "AES_CM_128_HMAC_SHA1_80") &&
		    sipe_strequal(tokens[2], "inline") &&
		    !tokens[5]) {
			gsize key_len;
			*key = g_base64_decode(tokens[3], &key_len);
			if (key_len != SIPE_SRTP_KEY_LEN) {
				g_free(*key);
				*key = NULL;
			}
			*key_id = atoi(tokens[0]);
		}

		g_strfreev(tokens);

		if (*key) {
			break;
		}
	}
}

struct sdpmsg *
sdpmsg_parse_msg(const gchar *msg)
{
	struct sdpmsg *smsg = g_new0(struct sdpmsg, 1);
	GSList *i;

	if (!parse_attributes(smsg, msg)) {
		sdpmsg_free(smsg);
		return NULL;
	}

	smsg->ice_version = SIPE_ICE_NO_ICE;
	for (i = smsg->media; i; i = i->next) {
		struct sdpmedia *media = i->data;
		SipeMediaType type;
		SipeIceVersion detected_ice_version;

		if (!parse_candidates(media->attributes, &detected_ice_version,
				      &media->candidates)) {
			sdpmsg_free(smsg);
			return NULL;
		}

		if (media->port != 0) {
			smsg->ice_version = detected_ice_version;

			if (!media->candidates) {
				// No a=candidate in SDP message, this seems to be MSOC 2005
				media->candidates = create_legacy_candidates(smsg->ip, media->port);
			}
		}

		if (sipe_strequal(media->name, "audio"))
			type = SIPE_MEDIA_AUDIO;
		else if (sipe_strequal(media->name, "video"))
			type = SIPE_MEDIA_VIDEO;
		else if (sipe_strequal(media->name, "data"))
			type = SIPE_MEDIA_APPLICATION;
		else if (sipe_strequal(media->name, "applicationsharing"))
			type = SIPE_MEDIA_APPLICATION;
		else {
			// Unknown media type
			sdpmsg_free(smsg);
			return NULL;
		}

		if (!parse_codecs(media->attributes, type, &media->codecs)) {
			sdpmsg_free(smsg);
			return NULL;
		}

		parse_encryption_key(media->attributes, &media->encryption_key,
				&media->encryption_key_id);
	}

	return smsg;
}

static gchar *
codecs_to_string(GSList *codecs)
{
	GString *result = g_string_new(NULL);

	for (; codecs; codecs = codecs->next) {
		struct sdpcodec *c = codecs->data;
		GSList *params = c->parameters;

		g_string_append_printf(result,
				       "a=rtpmap:%d %s/%d\r\n",
				       c->id,
				       c->name,
				       c->clock_rate);

		if (params) {
			GString *param_str = g_string_new(NULL);
			int written_params = 0;

			g_string_append_printf(param_str, "a=fmtp:%d", c->id);

			for (; params; params = params->next) {
				struct sipnameval* par = params->data;
				if (sipe_strequal(par->name, "farsight-send-profile")) {
					// Lync AVMCU doesn't like this property.
					continue;
				}

				g_string_append_printf(param_str, " %s=%s",
						       par->name, par->value);
				++written_params;
			}

			g_string_append(param_str, "\r\n");

			if (written_params > 0) {
				g_string_append(result, param_str->str);
			}

			g_string_free(param_str, TRUE);
		}
	}

	return g_string_free(result, FALSE);
}

static gchar *
codec_ids_to_string(GSList *codecs)
{
	GString *result = g_string_new(NULL);

	for (; codecs; codecs = codecs->next) {
		struct sdpcodec *c = codecs->data;
		g_string_append_printf(result, " %d", c->id);
	}

	return g_string_free(result, FALSE);
}

static gchar *
base64_unpad(const gchar *str)
{
	gchar *result = g_strdup(str);
	gchar *ptr;

	for (ptr = result + strlen(result); ptr != result; --ptr) {
		if (*(ptr - 1) != '=') {
			*ptr = '\0';
			break;
		}
	}

	return result;
}

static gchar *
candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
{
	GString *result = g_string_new("");
	GSList *i;
	GSList *processed_tcp_candidates = NULL;

	for (i = candidates; i; i = i->next) {
		struct sdpcandidate *c = i->data;
		const gchar *protocol;
		const gchar *type;
		gchar *related = NULL;

		if (ice_version == SIPE_ICE_RFC_5245) {

			switch (c->protocol) {
				case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
					protocol = "TCP-ACT";
					break;
				case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
					protocol = "TCP-PASS";
					break;
				case SIPE_NETWORK_PROTOCOL_UDP:
					protocol = "UDP";
					break;
				default:
					/* error unknown/unsupported type */
					protocol = "UNKNOWN";
					break;
			}

			switch (c->type) {
				case SIPE_CANDIDATE_TYPE_HOST:
					type = "host";
					break;
				case SIPE_CANDIDATE_TYPE_RELAY:
					type = "relay";
					break;
				case SIPE_CANDIDATE_TYPE_SRFLX:
					type = "srflx";
					break;
				case SIPE_CANDIDATE_TYPE_PRFLX:
					type = "prflx";
					break;
				default:
					/* error unknown/unsupported type */
					type = "unknown";
					break;
			}

			switch (c->type) {
				case SIPE_CANDIDATE_TYPE_RELAY:
				case SIPE_CANDIDATE_TYPE_SRFLX:
				case SIPE_CANDIDATE_TYPE_PRFLX:
					related = g_strdup_printf("raddr %s rport %d",
								  c->base_ip,
								  c->base_port);
					break;
				default:
					break;
			}

			g_string_append_printf(result,
					       "a=candidate:%s %u %s %u %s %d typ %s %s\r\n",
					       c->foundation,
					       c->component,
					       protocol,
					       c->priority,
					       c->ip,
					       c->port,
					       type,
					       related ? related : "");
			g_free(related);

		} else if (ice_version == SIPE_ICE_DRAFT_6) {
			gchar *username;
			gchar *password;

			switch (c->protocol) {
				case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
				case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE: {
					GSList *prev_cand = processed_tcp_candidates;
					for (; prev_cand; prev_cand = prev_cand->next) {
						struct sdpcandidate *c2 = (struct sdpcandidate *)prev_cand->data;

						if (sipe_strequal(c->ip, c2->ip) &&
						    c->component == c2->component) {
							break;
						}
					}

					if (prev_cand) {
						protocol = NULL;
					} else {
						protocol = "TCP";
						processed_tcp_candidates =
							g_slist_append(processed_tcp_candidates, c);
					}
					break;
				}
				case SIPE_NETWORK_PROTOCOL_UDP:
					protocol = "UDP";
					break;
				default:
					/* unknown/unsupported type, ignore */
					protocol = NULL;
					break;
			}

			if (!protocol) {
				continue;
			}

			username = base64_unpad(c->username);
			password = base64_unpad(c->password);

			g_string_append_printf(result,
					       "a=candidate:%s %u %s %s 0.%u %s %d\r\n",
					       username,
					       c->component,
					       password,
					       protocol,
					       c->priority,
					       c->ip,
					       c->port);

			g_free(username);
			g_free(password);
		}
	}

	g_slist_free(processed_tcp_candidates);

	return g_string_free(result, FALSE);
}

static gint
remote_candidates_sort_cb(struct sdpcandidate *c1, struct sdpcandidate *c2)
{
	return c1->component - c2->component;
}

static gchar *
remote_candidates_to_string(GSList *candidates, SipeIceVersion ice_version)
{
	GString *result = g_string_new("");

	if (candidates) {
		// Sort the candidates by increasing component IDs.
		candidates = g_slist_sort(candidates,
					  (GCompareFunc)remote_candidates_sort_cb);

		if (ice_version == SIPE_ICE_RFC_5245) {
			GSList *i;
			g_string_append(result, "a=remote-candidates:");

			for (i = candidates; i; i = i->next) {
				struct sdpcandidate *c = i->data;
				g_string_append_printf(result, "%u %s %u ",
						       c->component, c->ip, c->port);
			}

			g_string_append(result, "\r\n");
		} else if (ice_version == SIPE_ICE_DRAFT_6) {
			struct sdpcandidate *c = candidates->data;
			g_string_append_printf(result, "a=remote-candidate:%s\r\n",
					       c->username);
		}
	}

	return g_string_free(result, FALSE);
}

static gchar *
attributes_to_string(GSList *attributes)
{
	GString *result = g_string_new("");

	for (; attributes; attributes = attributes->next) {
		struct sipnameval *a = attributes->data;
		g_string_append_printf(result, "a=%s", a->name);
		if (!sipe_strequal(a->value, ""))
			g_string_append_printf(result, ":%s", a->value);
		g_string_append(result, "\r\n");
	}

	return g_string_free(result, FALSE);
}

static gchar *
media_to_string(const struct sdpmsg *msg, const struct sdpmedia *media)
{
	gchar *media_str;

	gchar *transport_profile = NULL;

	gchar *media_conninfo = NULL;

	gchar *codecs_str = NULL;
	gchar *codec_ids_str = codec_ids_to_string(media->codecs);

	gchar *candidates_str = NULL;
	gchar *remote_candidates_str = NULL;

	gchar *attributes_str = NULL;
	gchar *credentials = NULL;

	gchar *crypto = NULL;

	gboolean uses_tcp_transport = TRUE;

	if (media->port != 0) {
		if (!sipe_strequal(msg->ip, media->ip)) {
			media_conninfo = g_strdup_printf("c=IN %s %s\r\n",
							 sipe_utils_ip_sdp_address_marker(media->ip),
							 media->ip);
		}

		codecs_str = codecs_to_string(media->codecs);
		candidates_str = candidates_to_string(media->candidates, msg->ice_version);
		remote_candidates_str = remote_candidates_to_string(media->remote_candidates,
								    msg->ice_version);

		if (media->remote_candidates) {
			struct sdpcandidate *c = media->remote_candidates->data;
			uses_tcp_transport =
				c->protocol == SIPE_NETWORK_PROTOCOL_TCP_ACTIVE ||
				c->protocol == SIPE_NETWORK_PROTOCOL_TCP_PASSIVE ||
				c->protocol == SIPE_NETWORK_PROTOCOL_TCP_SO;
		} else {
			GSList *candidates = media->candidates;
			for (; candidates; candidates = candidates->next) {
				struct sdpcandidate *c = candidates->data;
				if (c->protocol == SIPE_NETWORK_PROTOCOL_UDP) {
					uses_tcp_transport = FALSE;
					break;
				}
			}
		}

		attributes_str = attributes_to_string(media->attributes);

		if (msg->ice_version == SIPE_ICE_RFC_5245 && media->candidates) {
			struct sdpcandidate *c = media->candidates->data;

			credentials = g_strdup_printf("a=ice-ufrag:%s\r\n"
						      "a=ice-pwd:%s\r\n",
						      c->username,
						      c->password);
		}

		if (media->encryption_key) {
			gchar *key_encoded = g_base64_encode(media->encryption_key, SIPE_SRTP_KEY_LEN);
			crypto = g_strdup_printf("a=crypto:%d AES_CM_128_HMAC_SHA1_80 inline:%s|2^31\r\n",
					media->encryption_key_id, key_encoded);
			g_free(key_encoded);
		}
	}

	transport_profile = g_strdup_printf("%sRTP/%sAVP",
					    uses_tcp_transport ? "TCP/" : "",
					    media->encryption_active ? "S" : "");

	media_str = g_strdup_printf("m=%s %d %s%s\r\n"
				    "%s"
				    "%s"
				    "%s"
				    "%s"
				    "%s"
				    "%s"
				    "%s",
				    media->name, media->port, transport_profile, codec_ids_str,
				    media_conninfo ? media_conninfo : "",
				    candidates_str ? candidates_str : "",
				    crypto ? crypto : "",
				    remote_candidates_str ? remote_candidates_str : "",
				    codecs_str ? codecs_str : "",
				    attributes_str ? attributes_str : "",
				    credentials ? credentials : "");

	g_free(transport_profile);
	g_free(media_conninfo);
	g_free(codecs_str);
	g_free(codec_ids_str);
	g_free(candidates_str);
	g_free(remote_candidates_str);
	g_free(attributes_str);
	g_free(credentials);
	g_free(crypto);

	return media_str;
}

gchar *
sdpmsg_to_string(const struct sdpmsg *msg)
{
	GString *body = g_string_new(NULL);
	GSList *i;
	const gchar *marker = sipe_utils_ip_sdp_address_marker(msg->ip);

	g_string_append_printf(
		body,
		"v=0\r\n"
		"o=- 0 0 IN %s %s\r\n"
		"s=session\r\n"
		"c=IN %s %s\r\n"
		"b=CT:99980\r\n"
		"t=0 0\r\n",
		marker, msg->ip,
		marker, msg->ip);


	for (i = msg->media; i; i = i->next) {
		gchar *media_str = media_to_string(msg, i->data);
		g_string_append(body, media_str);
		g_free(media_str);
	}

	return g_string_free(body, FALSE);
}

static struct sdpcandidate *
sdpcandidate_copy(struct sdpcandidate *candidate)
{
	if (candidate) {
		struct sdpcandidate *copy = g_new0(struct sdpcandidate, 1);

		copy->foundation = g_strdup(candidate->foundation);
		copy->component  = candidate->component;
		copy->type       = candidate->type;
		copy->protocol   = candidate->protocol;
		copy->priority   = candidate->priority;
		copy->ip         = g_strdup(candidate->ip);
		copy->port       = candidate->port;
		copy->base_ip    = g_strdup(candidate->base_ip);
		copy->base_port  = candidate->base_port;
		copy->username   = g_strdup(candidate->username);
		copy->password   = g_strdup(candidate->password);

		return copy;
	} else
		return NULL;
}

void
sdpcandidate_free(struct sdpcandidate *candidate)
{
	if (candidate) {
		g_free(candidate->foundation);
		g_free(candidate->ip);
		g_free(candidate->base_ip);
		g_free(candidate->username);
		g_free(candidate->password);
		g_free(candidate);
	}
}

void
sdpcodec_free(struct sdpcodec *codec)
{
	if (codec) {
		g_free(codec->name);
		sipe_utils_nameval_free(codec->parameters);
		g_free(codec);
	}
}

void
sdpmedia_free(struct sdpmedia *media)
{
	if (media) {
		g_free(media->name);
		g_free(media->ip);

		sipe_utils_nameval_free(media->attributes);

		sipe_utils_slist_free_full(media->candidates,
				  (GDestroyNotify) sdpcandidate_free);
		sipe_utils_slist_free_full(media->codecs,
				  (GDestroyNotify) sdpcodec_free);
		sipe_utils_slist_free_full(media->remote_candidates,
				  (GDestroyNotify) sdpcandidate_free);

		g_free(media->encryption_key);

		g_free(media);
	}
}

void
sdpmsg_free(struct sdpmsg *msg)
{
	if (msg) {
		g_free(msg->ip);
		sipe_utils_slist_free_full(msg->media,
				  (GDestroyNotify) sdpmedia_free);
		g_free(msg);
	}
}

/*
  Local Variables:
  mode: c
  c-file-style: "bsd"
  indent-tabs-mode: t
  tab-width: 8
  End:
*/