/*
 * Copyright (c) 2010-2021 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <algorithm>

#include "linphone/utils/utils.h"
#include "c-wrapper/internal/c-tools.h"
#include "sal/sal_stream_description.h"
#include "utils/payload-type-handler.h"
#include "bellesip_sal/sal_impl.h"

LINPHONE_BEGIN_NAMESPACE

#define keywordcmp(key,b) strncmp(key,b,sizeof(key))

PotentialCfgGraph::media_description_config::key_type SalStreamDescription::actualConfigurationIndex{ 0 };

bool SalConfigurationCmp::operator()(const PotentialCfgGraph::media_description_config::key_type& lhs, const PotentialCfgGraph::media_description_config::key_type& rhs) const { 
	// Compare the key minus 1 because 0 is the actual configuration hence it has the lowest priority.
	// When iterating over the configuration map, it should be the last element taken into account
	return ((lhs-1) < (rhs-1));
}

SalStreamDescription::SalStreamDescription(){
	// By default, the current index points to the actual configuration
	cfgIndex = SalStreamDescription::actualConfigurationIndex;
	cfgs.clear();
	already_assigned_payloads.clear();

	ice_candidates.clear();
	ice_remote_candidates.clear();
}

SalStreamDescription::~SalStreamDescription(){
	PayloadTypeHandler::clearPayloadList(already_assigned_payloads);
}

SalStreamDescription::SalStreamDescription(const SalStreamDescription & other){
	name = other.name;
	type = other.type;
	typeother = other.typeother;
	rtp_addr = other.rtp_addr;
	rtcp_addr = other.rtcp_addr;
	rtp_port = other.rtp_port;
	rtcp_port = other.rtcp_port;
	acaps = other.acaps;
	tcaps = other.tcaps;
	for (const auto & cfg : other.cfgs) {
		cfgs.insert(cfg);
	}
	for (const auto & cfg : other.unparsed_cfgs) {
		unparsed_cfgs.insert(cfg);
	}
	for (const auto & pt : other.already_assigned_payloads) {
		already_assigned_payloads.push_back(payload_type_clone(pt));
	}
	bandwidth = other.bandwidth;
	multicast_role = other.multicast_role;

	ice_candidates = other.ice_candidates;
	ice_remote_candidates = other.ice_remote_candidates;
	ice_ufrag = other.ice_ufrag;
	ice_pwd = other.ice_pwd;
	ice_mismatch = other.ice_mismatch;

}

SalStreamDescription::SalStreamDescription(const SalMediaDescription * salMediaDesc, const belle_sdp_session_description_t  *sdp, const belle_sdp_media_description_t *media_desc) : SalStreamDescription() {
	fillStreamDescriptionFromSdp(salMediaDesc, sdp, media_desc);
}

SalStreamDescription::SalStreamDescription(const SalMediaDescription * salMediaDesc, const belle_sdp_session_description_t  *sdp, const belle_sdp_media_description_t *media_desc, const SalStreamDescription::raw_capability_negotiation_attrs_t & attrs) : SalStreamDescription(salMediaDesc, sdp, media_desc) {
	// Create potential configurations
	fillPotentialConfigurationsFromPotentialCfgGraph(attrs.cfgs);
}

void SalStreamDescription::fillStreamDescriptionFromSdp(const SalMediaDescription * salMediaDesc, const belle_sdp_session_description_t  *sdp, const belle_sdp_media_description_t *media_desc) {
	belle_sdp_connection_t* cnx;
	belle_sdp_media_t* media;
	belle_sdp_attribute_t* attribute;
	const char* value;
	std::string mtype;

	media=belle_sdp_media_description_get_media ( media_desc );

	if ( ( cnx=belle_sdp_media_description_get_connection ( media_desc ) ) && belle_sdp_connection_get_address ( cnx ) ) {
		rtp_addr = L_C_TO_STRING(belle_sdp_connection_get_address ( cnx ));
	}

	rtp_port=belle_sdp_media_get_media_port ( media );

	mtype = belle_sdp_media_get_media_type ( media );
	// Make mtype lowercase to emulate case insensitive comparison
	std::transform(mtype.begin(), mtype.end(), mtype.begin(), ::tolower);
	if ( mtype.compare( "audio" ) == 0 ) {
		type=SalAudio;
	} else if (mtype.compare( "video" ) == 0 ) {
		type=SalVideo;
	} else if (mtype.compare( "text" ) == 0 ) {
		type=SalText;
	} else {
		type=SalOther;
		typeother = mtype;
	}

	/* Get media specific RTCP attribute */
	rtcp_addr = rtp_addr;
	rtcp_port = rtp_port + 1;

	attribute=belle_sdp_media_description_get_attribute(media_desc,"rtcp");
	if (attribute && (value=belle_sdp_attribute_get_value(attribute))!=NULL){
		char *tmp = (char *)ms_malloc0(strlen(value));
		int nb = sscanf(value, "%d IN IP4 %s", &rtcp_port, tmp);
		if (nb == 1) {
			/* SDP rtcp attribute only contains the port */
		} else if (nb == 2) {
			rtcp_addr = L_C_TO_STRING(tmp);
		} else {
			ms_warning("sdp has a strange a=rtcp line (%s) nb=%i", value, nb);
		}
		ms_free(tmp);
	}

	if ( belle_sdp_media_description_get_bandwidth ( media_desc,"AS" ) >0 ) {
		bandwidth=belle_sdp_media_description_get_bandwidth ( media_desc,"AS" );
	}

	createActualCfg(salMediaDesc, sdp, media_desc);
}

void SalStreamDescription::fillStreamDescriptionFromSdp(const SalMediaDescription * salMediaDesc, const belle_sdp_session_description_t  *sdp, const belle_sdp_media_description_t *media_desc, const SalStreamDescription::raw_capability_negotiation_attrs_t & attrs) {

	// Populate stream global parameters and actual configuration
	fillStreamDescriptionFromSdp(salMediaDesc, sdp, media_desc);

	if (salMediaDesc->supportCapabilityNegotiation()) {

		for (const auto & acap : attrs.acaps) {
			acaps[acap->index] = std::make_pair(acap->name, acap->value);
		}

		for (const auto & tcap : attrs.tcaps) {
			tcaps[tcap->index] = tcap->value;
		}

		for (const auto & cfg : attrs.unparsed_cfgs) {
			unparsed_cfgs.insert(cfg);
		}
		// Create potential configurations
		fillPotentialConfigurationsFromPotentialCfgGraph(attrs.cfgs);
	}

}

void SalStreamDescription::fillPotentialConfigurationsFromPotentialCfgGraph(const PotentialCfgGraph::media_description_config & sdpCfgs) {

	// Iterate over the potential configuration
	for (const auto & SDPMediaDescriptionCfgPair : sdpCfgs) {
		// This is the configuration ondex
		const auto & idx = SDPMediaDescriptionCfgPair.first;

		const auto & cfgAttributeList = SDPMediaDescriptionCfgPair.second;

		tcap_map_t protoMap;
		for (const auto & tAttr : cfgAttributeList.tcap) {
			const auto & cap = tAttr.cap.lock();
			if (cap) {
				protoMap[cap->index] = cap->value;
			} else {
				lError() << "Unable to retrieve transport capability from attribute list";
			}
		}

		std::list<acap_map_t> attrList;
		for (const auto & aAttrList : cfgAttributeList.acap) {
			acap_map_t attrs;
			for (const auto & aAttr : aAttrList) {
				const auto & cap = aAttr.cap.lock();
				if (cap) {
					const auto & capIdx = cap->index;
					const auto & capValue = cap->value;
					const auto & capName = cap->name;
					attrs.insert({capIdx, std::make_pair(capName, capValue)});
				} else {
					lError() << "Unable to retrieve transport capability from attribute list";
				}
			}
			if (!attrs.empty()) {
				attrList.push_back(attrs);
			}
		}

		addSupportedEncryptionFromSdp(protoMap, attrList);
		createPotentialConfigurationAtIdx(idx, protoMap, attrList, cfgAttributeList.delete_session_attributes, cfgAttributeList.delete_media_attributes);
	}
}

const SalStreamDescription::tcap_map_t::value_type & SalStreamDescription::encryptionToTcap(const SalStreamDescription::tcap_map_t & caps, const LinphoneMediaEncryption encEnum, const bool avpf) {
	const auto & it = std::find_if(caps.cbegin(), caps.cend(), [&avpf, &encEnum] (const auto & cap) {
		return (cap.second.compare(sal_media_proto_to_string(linphone_media_encryption_to_sal_media_proto(encEnum, ((avpf) ? true : false)))) == 0);
	});
	if (it != caps.end()) {
		return *it;
	}

	return Utils::getEmptyConstRefObject<SalStreamDescription::tcap_map_t::value_type>();
}

void SalStreamDescription::addSupportedEncryptionFromSdp(const SalStreamDescription::tcap_map_t & protoMap, const std::list<SalStreamDescription::acap_map_t> & attrList) {

	bool haveZrtpHash = false;
	for (const auto & attrs : attrList) {
		const auto zrtpHashIt = std::find_if(attrs.cbegin(), attrs.cend(), [] (const auto & attr) {
			// attr is a map<unsigned int (attr index), std::pair<std::string (name), std::string (value)>>
			return (attr.second.first.compare("zrtp-hash") == 0);
		});

		haveZrtpHash |= (zrtpHashIt != attrs.cend());
	}

	LinphoneMediaEncryption enc = LinphoneMediaEncryptionNone;
	for (const auto & tAttr : protoMap) {
		auto protoStr = tAttr.second;
		enc = sal_media_proto_to_linphone_media_encryption(sal_media_proto_from_string(protoStr.c_str()), (haveZrtpHash ? true : false));
		supportedEncryption.push_front(enc);
		lInfo() << "Adding encryption " << linphone_media_encryption_to_string(enc) << " to stream " << this;
	}

	// Delete duplicates
	supportedEncryption.unique();
}

const std::list<LinphoneMediaEncryption> SalStreamDescription::getSupportedEncryptionsInPotentialCfgs() const {
	// Last element of the list is the encryption of the actual configuration
	return {supportedEncryption.cbegin(), std::prev(supportedEncryption.cend(),1)}; 
}

void SalStreamDescription::createPotentialConfigurationAtIdx(const unsigned int & idx, const SalStreamDescription::tcap_map_t & tcaps, const std::list<SalStreamDescription::acap_map_t> & attrList, const bool delete_session_attributes, const bool delete_media_attributes) {

	auto baseCfg = createBasePotentialCfg();

	baseCfg.delete_media_attributes = delete_media_attributes;
	baseCfg.delete_session_attributes = delete_session_attributes;

	for (const auto & protoEl : tcaps) {
		const auto & protoIdx = protoEl.first;
		const auto & protoValue = protoEl.second;
		const auto proto = sal_media_proto_from_string(protoValue.c_str());
		baseCfg.tcapIndex = protoIdx;
		baseCfg.proto = proto;
		std::string protoString = (proto == SalProtoOther) ? protoValue : std::string();
		std::transform(protoString.begin(), protoString.end(), protoString.begin(), ::toupper);
		baseCfg.proto_other = protoString;
		if (baseCfg.hasAvpf()) {
			baseCfg.enableAvpfForStream();
		} else {
			baseCfg.disableAvpfForStream();
		}

		bool haveZrtpHash = false;
		for (const auto & attrs : attrList) {
			const auto zrtpHashIt = std::find_if(attrs.cbegin(), attrs.cend(), [] (const auto & attr) {
				// attr is a map<unsigned int (attr index), std::pair<std::string (name), std::string (value)>>
				return (attr.second.first.compare("zrtp-hash") == 0);
			});

			haveZrtpHash |= (zrtpHashIt != attrs.cend());
		}
		const LinphoneMediaEncryption enc = sal_media_proto_to_linphone_media_encryption(proto, haveZrtpHash);
		auto cfg = addAcapsToConfiguration(baseCfg, enc, attrList);

		cfg.index = idx;
		insertOrMergeConfiguration(idx, cfg);
	}
}

void SalStreamDescription::createPotentialConfiguration(const SalStreamDescription::tcap_map_t & protoMap, const std::list<SalStreamDescription::acap_map_t> & attrList, const bool delete_session_attributes, const bool delete_media_attributes) {

	auto baseCfg = createBasePotentialCfg();

	baseCfg.delete_media_attributes = delete_media_attributes;
	baseCfg.delete_session_attributes = delete_session_attributes;

	std::list<SalStreamDescription::cfg_map::mapped_type> cfgList;
	if (protoMap.empty()) {
		for (const auto & attrs : attrList) {
			auto cfg = baseCfg;
			std::list<unsigned int> cfgAcaps;
			for (const auto & attr : attrs) {
				const auto & capIndex = attr.first;
				cfgAcaps.push_back(capIndex);
			}
			cfg.acapIndexes.push_back(cfgAcaps);
			cfgList.push_back(cfg);
		}
	} else {
		const auto supportedEncs = getSupportedEncryptionsInPotentialCfgs();
		unsigned int idx = getFreeCfgIdx();
		for (const auto avpf : {true, false}) {
			for (const auto & enc : supportedEncs) {
				const auto & protoEl = SalStreamDescription::encryptionToTcap(protoMap, enc, avpf);
				if (protoEl != Utils::getEmptyConstRefObject<SalStreamDescription::tcap_map_t::value_type>()) {
					const auto & protoIdx = protoEl.first;
					const auto & protoValue = protoEl.second;
					const auto proto = sal_media_proto_from_string(protoValue.c_str());
					baseCfg.tcapIndex = protoIdx;
					baseCfg.proto = proto;
					std::string protoString = (proto == SalProtoOther) ? protoValue : std::string();
					std::transform(protoString.begin(), protoString.end(), protoString.begin(), ::toupper);
					baseCfg.proto_other = protoString;
					switch (proto) {
						case SalProtoRtpAvpf:
						case SalProtoRtpSavpf:
						case SalProtoUdpTlsRtpSavpf:
							baseCfg.enableAvpfForStream();
							break;
						default:
							baseCfg.disableAvpfForStream();
							break;
					}
					auto cfg = addAcapsToConfiguration(baseCfg, enc, attrList);
					cfg.index = idx;
					cfgList.push_back(cfg);
					idx++;
				}
			}
		}
	}

	for (auto & cfg : cfgList) {
		insertOrMergeConfiguration(cfg.index, cfg);
	}

}

SalStreamConfiguration SalStreamDescription::createBasePotentialCfg() const {
	auto baseCfg = getActualConfiguration();
	// Clear encryption flags
	// ZRTP
	baseCfg.haveZrtpHash = 0;
	memset(baseCfg.zrtphash, 0, sizeof(baseCfg.zrtphash));
	// SRTP
	baseCfg.crypto.clear();
	baseCfg.acapIndexes.clear();
	// DTLS
	baseCfg.dtls_fingerprint.clear();
	baseCfg.dtls_role = SalDtlsRoleInvalid;
	baseCfg.rtp_ssrc = 0;
	baseCfg.rtcp_cname.clear();

	return baseCfg;
}

void SalStreamDescription::insertOrMergeConfiguration(const unsigned & idx, const SalStreamConfiguration & cfg) {
	const auto sameCfg = std::find_if(cfgs.cbegin(), cfgs.cend(), [&cfg, this](const auto & currentCfg) {
		// Only potential configurations should be parsed - it is allowed to add a potential configuration identical to the actual one
		return ((currentCfg.first != this->getActualConfigurationIndex()) && (currentCfg.second == cfg));
	});

	if (sameCfg == cfgs.cend()) {
		auto ret = cfgs.insert(std::make_pair(idx, cfg));
		const auto & success = ret.second;
		if (success == false) {
			const auto & cfgPair = ret.first;
			const auto & cfgIndex = cfgPair->first;
			auto & existingCfg = cfgPair->second;
			const auto & existingCfgTcap = existingCfg.getTcapIndex();
			const auto & newCfgTcap = cfg.getTcapIndex();
			lInfo() << "Failed to insert potential configuration " << cfgIndex << " into the configuration map";
			if (existingCfgTcap == newCfgTcap) {
				existingCfg.mergeAcaps(cfg.getAcapIndexes());
				lInfo() << "Merging attribute capabiities with the existing one because both have the same transport protocol " << existingCfgTcap;
			} else {
				lError() << "Unable to merge merging attribute capabiities with the existing configuration as they have different transport protocol indexes - stored configuration " << existingCfgTcap << " new configuration " << newCfgTcap;
			}
		}
	}
}

SalStreamConfiguration SalStreamDescription::addAcapsToConfiguration(const SalStreamConfiguration & baseCfg, const LinphoneMediaEncryption & enc, const std::list<SalStreamDescription::acap_map_t> & attrList) const {

	auto cfg = baseCfg;

	for (const auto & attrs : attrList) {
		if (enc == LinphoneMediaEncryptionSRTP) {
			for (const auto & attr : attrs) {
				std::list<unsigned int> cfgAcaps;
				const auto & capNameValue = attr.second;
				const auto & capName = capNameValue.first;
				// For SRTP, only crypto attributes are valid in configurations
				if (capName.compare("crypto") == 0) {
					const auto & capIndex = attr.first;
					cfgAcaps.push_back(capIndex);
					cfg.acapIndexes.push_back(cfgAcaps);

					const auto & capValue = capNameValue.second;
					const auto keyEnc = SalStreamConfiguration::fillStrpCryptoAlgoFromString(capValue);
					if (keyEnc.algo!=MS_CRYPTO_SUITE_INVALID){
						cfg.crypto.push_back(keyEnc);
					}
				}
			}
		} else if (enc == LinphoneMediaEncryptionDTLS) {
			std::list<unsigned int> cfgAcaps;
			for (const auto & attr : attrs) {
				const auto & capIndex = attr.first;
				const auto & capNameValue = attr.second;
				const auto & capName = capNameValue.first;
				const auto & capValue = capNameValue.second;
				if (capName.compare("fingerprint") == 0) {
					cfgAcaps.push_back(capIndex);
					cfg.dtls_fingerprint = capValue;
				} else if (capName.compare("setup") == 0) {
					cfg.dtls_role = SalStreamConfiguration::getDtlsRoleFromSetupAttribute(capValue);
					cfgAcaps.push_back(capIndex);
				} else if (capName.compare("ssrc") == 0) {

					unsigned int rtpSsrc;
					char rtcpName[256]={0};
					const auto nb = sscanf ( capValue.c_str(), "%u cname:%s", &rtpSsrc, rtcpName);
					if (nb == 2) {
						cfgAcaps.push_back(capIndex);
						cfg.rtp_ssrc = rtpSsrc;
						cfg.rtcp_cname = rtcpName;
					} else {
						lError() << "Unable to retrieve rtp ssrc and rtcp cname from atribute " << capValue;
					}
				}
			}
			cfg.acapIndexes.push_back(cfgAcaps);
		} else if (enc == LinphoneMediaEncryptionZRTP) {
			for (const auto & attr : attrs) {
				const auto & capNameValue = attr.second;
				const auto & capName = capNameValue.first;
				std::list<unsigned int> cfgAcaps;
				if (capName.compare("zrtp-hash") == 0) {
					const auto & capIndex = attr.first;
					cfgAcaps.push_back(capIndex);
					cfg.acapIndexes.push_back(cfgAcaps);

					cfg.haveZrtpHash = true;
					const auto & capValue = capNameValue.second;
					strncpy((char *)cfg.zrtphash, capValue.c_str(), capValue.size());
				}
			}
		} else if (enc == LinphoneMediaEncryptionNone) {
			lInfo() << "No acap to add to potential configuration for encryption " << linphone_media_encryption_to_string(enc);
		} else {
			lInfo() << "Adding acaps to potential configuration for encryption " << linphone_media_encryption_to_string(enc);
			std::list<unsigned int> cfgAcaps;
			for (const auto & attr : attrs) {
				const auto & capIndex = attr.first;
				cfgAcaps.push_back(capIndex);
			}
			cfg.acapIndexes.push_back(cfgAcaps);
		}
	}

	return cfg;
}

const SalStreamDescription::cfg_map SalStreamDescription::getAllCfgs() const {
	return cfgs;
}

void SalStreamDescription::setProtoInCfg(SalStreamConfiguration & cfg, const std::string & str) {
	std::string protoOther;
	auto proto=SalProtoOther;
	if ( !str.empty() ) {
		auto protoAsString = str;
		// Make mtype lowercase to emulate case insensitive comparison
		std::transform(protoAsString.begin(), protoAsString.end(), protoAsString.begin(), ::toupper);
		if (protoAsString.compare("RTP/AVP") == 0) {
			proto = SalProtoRtpAvp;
		} else if (protoAsString.compare("RTP/SAVP") == 0) {
			proto = SalProtoRtpSavp;
		} else if (protoAsString.compare("RTP/AVPF") == 0) {
			proto = SalProtoRtpAvpf;
		} else if (protoAsString.compare("RTP/SAVPF") == 0) {
			proto = SalProtoRtpSavpf;
		} else if (protoAsString.compare("UDP/TLS/RTP/SAVP") == 0) {
			proto = SalProtoUdpTlsRtpSavp;
		} else if (protoAsString.compare("UDP/TLS/RTP/SAVPF") == 0) {
			proto = SalProtoUdpTlsRtpSavpf;
		} else {
			proto=SalProtoOther;
			protoOther = protoAsString;
		}
	}
	cfg.proto = proto;
	cfg.proto_other = protoOther;
}

void SalStreamDescription::createActualCfg(const SalMediaDescription * salMediaDesc, const belle_sdp_session_description_t  *sdp, const belle_sdp_media_description_t *media_desc) {
	belle_sdp_media_t* media;
	belle_sdp_attribute_t* attribute;
	belle_sip_list_t *custom_attribute_it;
	const char* value;

	media=belle_sdp_media_description_get_media ( media_desc );

	SalStreamConfiguration actualCfg;
	actualCfg.custom_sdp_attributes = NULL;

	/*copy dtls attributes from session descriptiun, might be overwritten stream by stream*/
	/*DTLS attributes can be defined at session level.*/
	SalDtlsRole session_role=SalDtlsRoleInvalid;
	value=belle_sdp_session_description_get_attribute_value(sdp,"setup");
	if (value){
		if (strncmp(value, "actpass", 7) == 0) {
			session_role = SalDtlsRoleUnset;
		} else if (strncmp(value, "active", 6) == 0) {
			session_role = SalDtlsRoleIsClient;
		} else if (strncmp(value, "passive", 7) == 0) {
			session_role = SalDtlsRoleIsServer;
		}
	}
	actualCfg.dtls_role = session_role;

	value=belle_sdp_session_description_get_attribute_value(sdp,"fingerprint");
	if (value) {
		actualCfg.dtls_fingerprint = L_C_TO_STRING(value);
	}

	const std::string protoStr = belle_sdp_media_get_protocol ( media );
	setProtoInCfg(actualCfg, protoStr);
	/* Read DTLS specific attributes : check is some are found in the stream description otherwise copy the session description one(which are at least set to Invalid) */
	if (((actualCfg.proto == SalProtoUdpTlsRtpSavpf) || (actualCfg.proto == SalProtoUdpTlsRtpSavp))) {
		attribute=belle_sdp_media_description_get_attribute(media_desc,"setup");
		if (attribute && (value=belle_sdp_attribute_get_value(attribute))!=NULL){
			if (strncmp(value, "actpass", 7) == 0) {
				actualCfg.dtls_role = SalDtlsRoleUnset;
			} else if (strncmp(value, "active", 6) == 0) {
				actualCfg.dtls_role = SalDtlsRoleIsClient;
			} else if (strncmp(value, "passive", 7) == 0) {
				actualCfg.dtls_role = SalDtlsRoleIsServer;
			}
		}
		if (actualCfg.dtls_role != SalDtlsRoleInvalid && (attribute=belle_sdp_media_description_get_attribute(media_desc,"fingerprint"))) {
			actualCfg.dtls_fingerprint = belle_sdp_attribute_get_value(attribute);
		}
	}

	belle_sdp_connection_t* cnx;
	if ( ( cnx=belle_sdp_media_description_get_connection ( media_desc ) ) && belle_sdp_connection_get_address ( cnx ) ) {
		actualCfg.ttl = belle_sdp_connection_get_ttl(cnx);
	}

	SalStreamDir dir=SalStreamInactive;
	if ( belle_sdp_media_description_get_attribute ( media_desc,"sendrecv" ) ) {
		dir=SalStreamSendRecv;
	} else if ( belle_sdp_media_description_get_attribute ( media_desc,"sendonly" ) ) {
		dir=SalStreamSendOnly;
	} else if ( belle_sdp_media_description_get_attribute ( media_desc,"recvonly" ) ) {
		dir=SalStreamRecvOnly;
	} else if ( belle_sdp_media_description_get_attribute ( media_desc,"inactive" ) ) {
		dir=SalStreamInactive;
	} else {
		dir=salMediaDesc->dir; /*takes default value if not present*/
	}
	actualCfg.dir = dir;

	actualCfg.rtcp_mux = belle_sdp_media_description_get_attribute(media_desc, "rtcp-mux") != NULL;
	actualCfg.bundle_only = belle_sdp_media_description_get_attribute(media_desc, "bundle-only") != NULL;

	attribute = belle_sdp_media_description_get_attribute(media_desc, "mid");
	if (attribute){
		value = belle_sdp_attribute_get_value(attribute);
		if (value)
			actualCfg.mid = L_C_TO_STRING(value);
	}

	actualCfg.payloads.clear();
	/* Get media payload types */
	sdpParsePayloadTypes(actualCfg, media_desc);

	/* Read crypto lines if any */
	if (actualCfg.hasSrtp()) {
		sdpParseMediaCryptoParameters(actualCfg, media_desc);
	}

	/* Read zrtp-hash attribute */
	if ((attribute=belle_sdp_media_description_get_attribute(media_desc,"zrtp-hash"))!=NULL) {
		if ((value=belle_sdp_attribute_get_value(attribute))!=NULL) {
		#if __GNUC__ > 7
			#pragma GCC diagnostic push
			#pragma GCC diagnostic ignored "-Wstringop-truncation"
		#endif
			strncpy((char *)(actualCfg.zrtphash), belle_sdp_attribute_get_value(attribute),sizeof(actualCfg.zrtphash));
			actualCfg.haveZrtpHash = 1;
		#if __GNUC__ > 7
			#pragma GCC diagnostic pop
		#endif
		}
	}

	/* Do we have Lime Ik attribute */
	if ((attribute=belle_sdp_media_description_get_attribute(media_desc,"Ik"))!=NULL) {
		if (belle_sdp_attribute_get_value(attribute)!=NULL) {
			actualCfg.haveLimeIk = 1;
		}
	}
	/* get ready to parse also lime-Ik */
	if ((attribute=belle_sdp_media_description_get_attribute(media_desc,"lime-Ik"))!=NULL) {
		if (belle_sdp_attribute_get_value(attribute)!=NULL) {
			actualCfg.haveLimeIk = 1;
		}
	}

	/* Get ICE candidate attributes if any */
	sdpParseMediaIceParameters(actualCfg, media_desc);

	bool has_avpf_attributes = sdpParseRtcpFbParameters(actualCfg, media_desc);

	/* Get RTCP-FB attributes if any */
	if (actualCfg.hasAvpf()) {
		actualCfg.enableAvpfForStream();
	}else if (has_avpf_attributes ){
		actualCfg.enableAvpfForStream();
		actualCfg.implicit_rtcp_fb = true;
	}

	/* Get RTCP-XR attributes if any */
	actualCfg.rtcp_xr = salMediaDesc->rtcp_xr; // Use session parameters if no stream parameters are defined
	sdp_parse_media_rtcp_xr_parameters(media_desc, &actualCfg.rtcp_xr);

	/* Get the ssrc used for conference if any */
	if ((attribute = belle_sdp_media_description_get_attribute(media_desc, "ssrc")) != NULL) {
		if ((value = belle_sdp_attribute_get_value(attribute)) != NULL) {
			sscanf(value, "%u", &actualCfg.conference_ssrc);
		}
	}

	/* Get the custom attributes, and parse some 'extmap'*/
	for (custom_attribute_it = belle_sdp_media_description_get_attributes(media_desc); custom_attribute_it != NULL; custom_attribute_it = custom_attribute_it->next) {
		belle_sdp_attribute_t *attr = (belle_sdp_attribute_t *)custom_attribute_it->data;
		const char *attr_name = belle_sdp_attribute_get_name(attr);
		const char *attr_value = belle_sdp_attribute_get_value(attr);
		actualCfg.custom_sdp_attributes = sal_custom_sdp_attribute_append(actualCfg.custom_sdp_attributes, attr_name, attr_value);

		if (strcasecmp(attr_name, "extmap") == 0){
			char *extmap_urn = (char*)bctbx_malloc0(strlen(attr_value) + 1);
			int value = 0;
			if (sscanf(attr_value, "%i %s", &value, extmap_urn) > 0){
				if (strcasecmp(extmap_urn, "urn:ietf:params:rtp-hdrext:sdes:mid") == 0){
					actualCfg.mid_rtp_ext_header_id = value;
				} else if (strcasecmp(extmap_urn, "urn:ietf:params:rtp-hdrext:csrc-audio-level") == 0) {
					actualCfg.mixer_to_client_extension_id = value;
				} else if (strcasecmp(extmap_urn, "urn:ietf:params:rtp-hdrext:ssrc-audio-level") == 0) {
					actualCfg.client_to_mixer_extension_id = value;
				}
			}
			bctbx_free(extmap_urn);
		}
	}

	LinphoneMediaEncryption enc = sal_media_proto_to_linphone_media_encryption(actualCfg.getProto(), (actualCfg.hasZrtpHash() ? true : false));
	supportedEncryption.push_front(enc);

	addActualConfiguration(actualCfg);
}

SalStreamDescription &SalStreamDescription::operator=(const SalStreamDescription & other){
	name = other.name;
	type = other.type;
	typeother = other.typeother;
	rtp_addr = other.rtp_addr;
	rtcp_addr = other.rtcp_addr;
	rtp_port = other.rtp_port;
	rtcp_port = other.rtcp_port;
	acaps = other.acaps;
	tcaps = other.tcaps;
	for (const auto & cfg : other.cfgs) {
		cfgs.insert(cfg);
	}
	for (const auto & cfg : other.unparsed_cfgs) {
		unparsed_cfgs.insert(cfg);
	}
	PayloadTypeHandler::clearPayloadList(already_assigned_payloads);
	for (const auto & pt : other.already_assigned_payloads) {
		already_assigned_payloads.push_back(payload_type_clone(pt));
	}
	bandwidth = other.bandwidth;
	multicast_role = other.multicast_role;

	ice_candidates = other.ice_candidates;
	ice_remote_candidates = other.ice_remote_candidates;
	ice_ufrag = other.ice_ufrag;
	ice_pwd = other.ice_pwd;
	ice_mismatch = other.ice_mismatch;

	return *this;
}

bool SalStreamDescription::operator==(const SalStreamDescription & other) const {
	return equal(other) == SAL_MEDIA_DESCRIPTION_UNCHANGED;
}

bool SalStreamDescription::operator!=(const SalStreamDescription & other) const {
	return !(*this == other);
}

int SalStreamDescription::compareToChosenConfiguration(const SalStreamDescription & other) const {
	int result = globalEqual(other);

	result |= compareConfigurations(other, getChosenConfigurationIndex(), other.getChosenConfigurationIndex());

	return result;
}

int SalStreamDescription::compareToActualConfiguration(const SalStreamDescription & other) const {
	int result = globalEqual(other);

	result |= compareConfigurations(other, getChosenConfigurationIndex(), other.getActualConfigurationIndex());

	return result;
}

int SalStreamDescription::compareConfigurations(const SalStreamDescription & other, const SalStreamDescription::cfg_map::key_type & thisKey, const SalStreamDescription::cfg_map::key_type & otherKey) const {

	const SalStreamConfiguration & thisCfg = getConfigurationAtIndex(thisKey);
	const SalStreamConfiguration & otherCfg = other.getConfigurationAtIndex(otherKey);
	int result = (thisCfg.equal(otherCfg));

	return result;
}

int SalStreamDescription::equal(const SalStreamDescription & other) const {
	int result = globalEqual(other);

	if (cfgs.size() != other.cfgs.size()) result |= SAL_MEDIA_DESCRIPTION_CONFIGURATION_CHANGED;

	for(auto cfg1 = cfgs.cbegin(), cfg2 = other.cfgs.cbegin(); (cfg1 != cfgs.cend() && cfg2 != other.cfgs.cend()); ++cfg1, ++cfg2){
		result |= cfg1->second.equal(cfg2->second);
	}

	/* ICE */
	if (ice_ufrag.compare(other.ice_ufrag) != 0 && !other.ice_ufrag.empty()) result |= SAL_MEDIA_DESCRIPTION_ICE_RESTART_DETECTED;
	if (ice_pwd.compare(other.ice_pwd) != 0 && !other.ice_pwd.empty()) result |= SAL_MEDIA_DESCRIPTION_ICE_RESTART_DETECTED;

	return result;
}

int SalStreamDescription::globalEqual(const SalStreamDescription & other) const {
	int result = SAL_MEDIA_DESCRIPTION_UNCHANGED;

	if (type != other.type) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED;

	// RTP
	if (rtp_addr.compare(other.rtp_addr) != 0) result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED;
	if ((rtp_addr.empty()==false) && (other.rtp_addr.empty()==false) && ms_is_multicast(L_STRING_TO_C(rtp_addr)) != ms_is_multicast(L_STRING_TO_C(other.rtp_addr)))
			result |= SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED;
	if (rtp_port != other.rtp_port) {
		if ((rtp_port == 0) || (other.rtp_port == 0)) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED;
		else result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED;
	}

	// RTCP
	if (rtcp_addr.compare(other.rtcp_addr) != 0) result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED;
	if (rtcp_port != other.rtcp_port) result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED;

	if (multicast_role != other.multicast_role) result |= SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED;

	return result;
}

bool SalStreamDescription::enabled() const {
	/* When the bundle-only attribute is present, a 0 rtp port doesn't mean that the stream is disabled.*/
	return rtp_port > 0 || isBundleOnly();
}

bool SalStreamDescription::isAcceptable() const {
	// Accept if enabled or RTP port is set to 0
	return enabled() || (rtp_port == 0);
}

void SalStreamDescription::disable(){
	rtp_port = 0;
	/* Remove potential bundle parameters. A disabled stream is moved out of the bundle. */
	cfgs[getChosenConfigurationIndex()].disable();
}

bool SalStreamDescription::hasAvpf() const {
	return getChosenConfiguration().hasAvpf();
}

bool SalStreamDescription::hasIpv6() const {
	return rtp_addr.find(':') != std::string::npos;
}

bool SalStreamDescription::hasImplicitAvpf() const {
	return getChosenConfiguration().hasImplicitAvpf();
}

bool SalStreamDescription::supportSrtp() const {
	for (const auto & cfgEl : cfgs) {
		const auto & cfg = cfgEl.second;
		if (cfg.hasSrtp()) {
			return true;
		}
	}
	return false;
}

bool SalStreamDescription::supportZrtp() const {
	for (const auto & cfgEl : cfgs) {
		const auto & cfg = cfgEl.second;
		if (cfg.hasZrtp()) {
			return true;
		}
	}
	return false;
}

bool SalStreamDescription::supportDtls() const {
	for (const auto & cfgEl : cfgs) {
		const auto & cfg = cfgEl.second;
		if (cfg.hasDtls()) {
			return true;
		}
	}
	return false;
}

bool SalStreamDescription::hasSrtp() const {
	return getChosenConfiguration().hasSrtp();
}

bool SalStreamDescription::hasDtls() const {
	return getChosenConfiguration().hasDtls();
}

bool SalStreamDescription::hasZrtp() const {
	return getChosenConfiguration().hasZrtp();
}

bool SalStreamDescription::hasLimeIk() const {
	return getChosenConfiguration().hasLimeIk();
}

const std::string & SalStreamDescription::getRtcpAddress() const {
	return rtcp_addr;
}

const int & SalStreamDescription::getRtcpPort() const {
	return rtcp_port;
}

const std::string & SalStreamDescription::getRtpAddress() const {
	return rtp_addr;
}

const int & SalStreamDescription::getRtpPort() const {
	return rtp_port;
}

const SalStreamType & SalStreamDescription::getType() const {
	return type;
}

const std::string SalStreamDescription::getTypeAsString() const {
	if (type==SalOther) return typeother;
	else return LinphonePrivate::Utils::toString(type);
}

void SalStreamDescription::setProto(const SalMediaProto & newProto) {
	cfgs[getChosenConfigurationIndex()].proto = newProto;
}

const SalMediaProto & SalStreamDescription::getProto() const {
	return getChosenConfiguration().getProto();
}

const std::string SalStreamDescription::getProtoAsString() const {
	return getChosenConfiguration().getProtoAsString();
}

void SalStreamDescription::setDirection(const SalStreamDir & newDir) {
	cfgs[getChosenConfigurationIndex()].dir = newDir;
}

SalStreamDir SalStreamDescription::getDirection() const {
	return getChosenConfiguration().getDirection();
}

const std::list<PayloadType*> & SalStreamDescription::getPayloads() const {
	return getChosenConfiguration().getPayloads();
}

const int & SalStreamDescription::getMaxRate() const {
	return getChosenConfiguration().getMaxRate();
}

SalCustomSdpAttribute * SalStreamDescription::getCustomSdpAttributes() const {
	return getChosenConfiguration().getCustomSdpAttributes();
}

void SalStreamDescription::setPtime(const int & ptime, const int & maxptime) {
	if (ptime > 0) {
		cfgs[getChosenConfigurationIndex()].ptime = ptime;
	}
	if (maxptime > 0) {
		cfgs[getChosenConfigurationIndex()].maxptime = maxptime;
	}
}

const std::vector<SalSrtpCryptoAlgo> & SalStreamDescription::getCryptos() const {
	return getChosenConfiguration().crypto;
}

const SalSrtpCryptoAlgo & SalStreamDescription::getCryptoAtIndex(const size_t & idx) const {
	try {
		const auto & el = getChosenConfiguration().crypto.at(idx);
		return el;
	} catch (std::out_of_range&) {
		lError() << "Unable to crypto at index " << idx << " in the available crypto vector";
		return Utils::getEmptyConstRefObject<SalSrtpCryptoAlgo>();
	}
}

void SalStreamDescription::setCrypto(const size_t & idx, const SalSrtpCryptoAlgo & newCrypto) {
	cfgs[getChosenConfigurationIndex()].crypto[idx] = newCrypto;
}

void SalStreamDescription::setSupportedEncryptions(const std::list<LinphoneMediaEncryption> & encryptionList) {
	supportedEncryption = encryptionList;
}

void SalStreamDescription::setupRtcpFb(const bool nackEnabled, const bool tmmbrEnabled, const bool implicitRtcpFb) {
	for (auto & cfg : cfgs) {
		cfg.second.rtcp_fb.generic_nack_enabled = nackEnabled;
		cfg.second.rtcp_fb.tmmbr_enabled = tmmbrEnabled;
		cfg.second.implicit_rtcp_fb = implicitRtcpFb;
	}
}

void SalStreamDescription::setupRtcpXr(const OrtpRtcpXrConfiguration & rtcpXr) {
	for (auto & cfg : cfgs) {
		memcpy(&cfg.second.rtcp_xr, &rtcpXr, sizeof(cfg.second.rtcp_xr));
	}
}

belle_sdp_media_description_t * SalStreamDescription::toSdpMediaDescription(const SalMediaDescription * salMediaDesc, belle_sdp_session_description_t *session_desc) const {
	belle_sdp_mime_parameter_t* mime_param;
	belle_sdp_media_description_t* media_desc;
	const char* dirStr=NULL;
	bool different_rtp_and_rtcp_addr;
	bool stream_enabled = enabled();

	const auto & actualCfg = getActualConfiguration();

	media_desc = belle_sdp_media_description_create ( L_STRING_TO_C(getTypeAsString())
				 ,rtp_port
				 ,1
				 ,L_STRING_TO_C(getProtoAsString())
				 ,NULL );
	if (!actualCfg.payloads.empty()) {
		for (const auto & pt : actualCfg.payloads) {
			mime_param= belle_sdp_mime_parameter_create ( pt->mime_type
					, payload_type_get_number ( pt )
					, pt->clock_rate
					, pt->channels>0 ? pt->channels : -1 );
			belle_sdp_mime_parameter_set_parameters ( mime_param,pt->recv_fmtp );
			if ( actualCfg.ptime>0 ) {
				belle_sdp_mime_parameter_set_ptime ( mime_param,actualCfg.ptime );
			}
			belle_sdp_media_description_append_values_from_mime_parameter ( media_desc,mime_param );
			belle_sip_object_unref ( mime_param );
		}
	} else {
		/* to comply with SDP we cannot have an empty payload type number list */
		/* as it happens only when mline is declined with a zero port, it does not matter to put whatever codec*/
		belle_sip_list_t* format = belle_sip_list_append(NULL,0);
		belle_sdp_media_set_media_formats(belle_sdp_media_description_get_media(media_desc),format);
	}

	/*only add a c= line within the stream description if address are differents*/
	if ((rtp_addr.empty()==false) && (salMediaDesc && (rtp_addr.compare(salMediaDesc->addr)!=0))){
		bool_t inet6;
		belle_sdp_connection_t *connection;
		if (rtp_addr.find(':')!=std::string::npos){
			inet6=true;
		}else inet6=false;
		connection = belle_sdp_connection_create("IN", inet6 ? "IP6" : "IP4", L_STRING_TO_C(rtp_addr));
		if (ms_is_multicast(L_STRING_TO_C(rtp_addr))) {
			/*remove session cline in case of multicast*/
			belle_sdp_session_description_set_connection(session_desc,NULL);
			if (inet6 == false)
				belle_sdp_connection_set_ttl(connection,actualCfg.ttl);
		}
		belle_sdp_media_description_set_connection(media_desc,connection);
	}

	if ( bandwidth>0 )
		belle_sdp_media_description_set_bandwidth ( media_desc,"AS",bandwidth );

	if (actualCfg.hasSrtp()) {
		/* add crypto lines */
		for ( const auto & crypto : actualCfg.crypto ) {
			const auto value = SalStreamConfiguration::cryptoToSdpValue(crypto);
			if (!value.empty()) {
				belle_sdp_media_description_add_attribute( media_desc,belle_sdp_attribute_create ("crypto", value.c_str()));
			}
		}
	}

	/* insert DTLS session attribute if needed */
	addDtlsAttributesToMediaDesc(actualCfg, media_desc);

	/* insert zrtp-hash attribute if needed */
	if (actualCfg.haveZrtpHash == 1) {
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("zrtp-hash", (const char *)(actualCfg.zrtphash)));
	}

	switch ( actualCfg.dir ) {
		case SalStreamSendRecv:
			/*dirStr="sendrecv";*/
			dirStr=NULL;
			break;
		case SalStreamRecvOnly:
			dirStr="recvonly";
			break;
		case SalStreamSendOnly:
			dirStr="sendonly";
			break;
		case SalStreamInactive:
			dirStr="inactive";
			break;
	}
	if ( dirStr ) belle_sdp_media_description_add_attribute ( media_desc,belle_sdp_attribute_create ( dirStr,NULL ) );

	if (actualCfg.rtcp_mux){
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create ("rtcp-mux",NULL ) );
	}
	addMidAttributesToSdp(actualCfg, media_desc);

	if (actualCfg.mixer_to_client_extension_id != 0) {
		char *value = bctbx_strdup_printf("%i urn:ietf:params:rtp-hdrext:csrc-audio-level", actualCfg.mixer_to_client_extension_id);
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("extmap", value));
		bctbx_free(value);
	}

	if (actualCfg.client_to_mixer_extension_id != 0) {
		char *value = bctbx_strdup_printf("%i urn:ietf:params:rtp-hdrext:ssrc-audio-level vad=off", actualCfg.client_to_mixer_extension_id);
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("extmap", value));
		bctbx_free(value);
	}

	if (actualCfg.proto != SalProtoUdpTlsRtpSavpf && actualCfg.proto != SalProtoUdpTlsRtpSavp && actualCfg.conference_ssrc) {
		char* ssrc_attribute = ms_strdup_printf("%u",actualCfg.conference_ssrc);
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("ssrc",ssrc_attribute));
		ms_free(ssrc_attribute);
	}

	if (rtp_port != 0) {
		different_rtp_and_rtcp_addr = (rtcp_addr.empty() == false) && (rtp_addr.compare(rtcp_addr) != 0);
		if ((rtcp_port != 0) && ((rtcp_port != (rtp_port + 1)) || (different_rtp_and_rtcp_addr == true))) {
			std::string rtcpAttrValue = std::to_string(rtcp_port);
			if (different_rtp_and_rtcp_addr == true) {
				rtcpAttrValue += " IN IP4 " + rtcp_addr;
			}
			belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("rtcp",rtcpAttrValue.c_str()));
		}
	}
	if (actualCfg.set_nortpproxy == true) {
		belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("nortpproxy","yes"));
	}
	if (ice_mismatch == true) {
		belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("ice-mismatch",NULL));
	} else {
		if (rtp_port != 0) {
			if (!ice_pwd.empty())
				belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("ice-pwd",L_STRING_TO_C(ice_pwd)));
			if (!ice_ufrag.empty())
				belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("ice-ufrag",L_STRING_TO_C(ice_ufrag)));
			addIceCandidatesToSdp(actualCfg, media_desc);
			addIceRemoteCandidatesToSdp(actualCfg, media_desc);
		}
	}

	if (stream_enabled && (actualCfg.hasAvpf() || actualCfg.hasImplicitAvpf())) {
		addRtcpFbAttributesToSdp(actualCfg, media_desc);
	}

	if (stream_enabled && (actualCfg.rtcp_xr.enabled == TRUE)) {
		char sastr[1024] = {0};
		char mastr[1024] = {0};
		size_t saoff = 0;
		size_t maoff = 0;
		const belle_sdp_attribute_t *session_attribute = belle_sdp_session_description_get_attribute(session_desc, "rtcp-xr");
		belle_sdp_attribute_t *media_attribute;
		if (session_attribute != NULL) {
			belle_sip_object_marshal((belle_sip_object_t*)session_attribute, sastr, sizeof(sastr), &saoff);
		}
		media_attribute = create_rtcp_xr_attribute(&actualCfg.rtcp_xr);
		if (media_attribute != NULL) {
			belle_sip_object_marshal((belle_sip_object_t*)media_attribute, mastr, sizeof(mastr), &maoff);
		}
		if (strcmp(sastr, mastr) != 0) {
			belle_sdp_media_description_add_attribute(media_desc, media_attribute);
		} else {
			belle_sip_object_unref((belle_sip_object_t*)media_attribute);
		}
	}

	if (actualCfg.custom_sdp_attributes) {
		belle_sdp_session_description_t *custom_desc = (belle_sdp_session_description_t *)actualCfg.custom_sdp_attributes;
		belle_sip_list_t *l = belle_sdp_session_description_get_attributes(custom_desc);
		belle_sip_list_t *elem;
		for (elem = l; elem != NULL; elem = elem->next) {
			belle_sdp_media_description_add_attribute(media_desc, (belle_sdp_attribute_t *)elem->data);
		}
	}

	if (salMediaDesc->supportCapabilityNegotiation()) {
		for (const auto & acap : acaps) {
			const auto & idx = acap.first;
			const auto & nameValuePair = acap.second;
			const auto & name = nameValuePair.first;
			const auto & value = nameValuePair.second;
			std::string acapValue = std::to_string(idx) + " " + name + ":" + value;
			belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("acap",acapValue.c_str()));
		}

		std::string tcapValue;
		SalStreamDescription::tcap_map_t::key_type prevIdx = 0;
		for (const auto & tcap : tcaps) {
			const auto & idx = tcap.first;
			const auto & value = tcap.second;
			if (salMediaDesc->tcapLinesMerged()) {
				if (tcapValue.empty()) {
					tcapValue = std::to_string(idx) + " " + value;
					prevIdx = idx;
				} else {
					if (idx == (prevIdx + 1)) {
						tcapValue += " " + value;
					} else {
						belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("tcap",tcapValue.c_str()));
						tcapValue = std::to_string(idx) + " " + value;
					}
					prevIdx = idx;
				}
			} else {
				tcapValue = std::to_string(idx) + " " + value;
				belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("tcap",tcapValue.c_str()));
			}
		}

		if (salMediaDesc->tcapLinesMerged() && !tcapValue.empty()) {
			belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("tcap",tcapValue.c_str()));
		}

		for (const auto & cfgPair : cfgs) {
			const auto & cfg = cfgPair.second;
			const auto & cfgSdpString = cfg.getSdpString();
			if (!cfgSdpString.empty()) {
				const auto & cfgIdx = cfg.index;
				// Add acfg or pcfg attributes if not the actual configuration or if the actual configuration refer to a potential configuration (member index is not the actual configuration index)
				std::string attrName;
				const auto & cfgKey = cfgPair.first;
				if (cfgKey != getActualConfigurationIndex()) {
					attrName = "pcfg";
				} else if (cfgKey == getActualConfigurationIndex() && (cfgIdx != getActualConfigurationIndex())) {
					attrName = "acfg";
				}
				if (!attrName.empty()) {
					const auto attrValue = std::to_string(cfgIdx) + " " + cfgSdpString;
					belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create(attrName.c_str(),attrValue.c_str()));
				}
			}
		}

	}

	return media_desc;
}

void SalStreamDescription::addDtlsAttributesToMediaDesc(const SalStreamConfiguration & cfg, belle_sdp_media_description_t *media_desc) const {

	/*
	 * rfc5576
	 * 4.1.  The "ssrc" Media Attribute
	 * <ssrc-id> is the synchronization source (SSRC) ID of the
	 * source being described, interpreted as a 32-bit unsigned integer in
	 * network byte order and represented in decimal.*/

	if ((cfg.proto == SalProtoUdpTlsRtpSavpf) || (cfg.proto == SalProtoUdpTlsRtpSavp)) {
		char* ssrc_attribute = ms_strdup_printf("%u cname:%s",cfg.rtp_ssrc,L_STRING_TO_C(cfg.rtcp_cname));
		if ((cfg.dtls_role != SalDtlsRoleInvalid) && (!cfg.dtls_fingerprint.empty())) {
			const auto setupAttrValue = SalStreamConfiguration::getSetupAttributeForDtlsRole(cfg.dtls_role);
			if (!setupAttrValue.empty()) {
				belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("setup",setupAttrValue.c_str()));
			}
			belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("fingerprint",L_STRING_TO_C(cfg.dtls_fingerprint)));
		}
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("ssrc",ssrc_attribute));
		ms_free(ssrc_attribute);
	}
}

bool SalStreamDescription::sdpParseRtcpFbParameters(SalStreamConfiguration & cfg, const belle_sdp_media_description_t *media_desc) {
	belle_sip_list_t *it;
	belle_sdp_attribute_t *attribute;
	belle_sdp_rtcp_fb_attribute_t *fb_attribute;
	int8_t pt_num;
	bool retval = false;

	/* Handle rtcp-fb attributes that concern all payload types. */
	for (it = belle_sdp_media_description_get_attributes(media_desc); it != NULL; it = it->next) {
		attribute = BELLE_SDP_ATTRIBUTE(it->data);
		if (keywordcmp("rtcp-fb", belle_sdp_attribute_get_name(attribute)) == 0) {
			fb_attribute = BELLE_SDP_RTCP_FB_ATTRIBUTE(attribute);
			if (belle_sdp_rtcp_fb_attribute_get_id(fb_attribute) == -1) {
				for (const auto & pt : cfg.payloads) {
					applyRtcpFbAttributeToPayload(cfg, fb_attribute, pt);
					retval = true;
				}
			}
		}
	}

	/* Handle rtcp-fb attributes that are specefic to a payload type. */
	for (it = belle_sdp_media_description_get_attributes(media_desc); it != NULL; it = it->next) {
		attribute = BELLE_SDP_ATTRIBUTE(it->data);
		if (keywordcmp("rtcp-fb", belle_sdp_attribute_get_name(attribute)) == 0) {
			fb_attribute = BELLE_SDP_RTCP_FB_ATTRIBUTE(attribute);
			pt_num = belle_sdp_rtcp_fb_attribute_get_id(fb_attribute);
			for (const auto & pt : cfg.payloads) {
				retval = true;
				if (payload_type_get_number(pt) == (int)pt_num) {
					applyRtcpFbAttributeToPayload(cfg, fb_attribute, pt);
				}
			}
		}
	}
	return retval;
}

void SalStreamDescription::sdpParsePayloadTypes(SalStreamConfiguration & cfg, const belle_sdp_media_description_t *media_desc) const {
	PayloadType *pt;
	PayloadTypeAvpfParams avpf_params;
	belle_sip_list_t* mime_param_it=NULL;
	belle_sdp_mime_parameter_t* mime_param;
	belle_sip_list_t* mime_params=belle_sdp_media_description_build_mime_parameters ( media_desc );
	memset(&avpf_params, 0, sizeof(avpf_params));
	for ( mime_param_it=mime_params
						; mime_param_it!=NULL
			; mime_param_it=mime_param_it->next ) {
		mime_param=BELLE_SDP_MIME_PARAMETER ( mime_param_it->data );

		pt=payload_type_new();
		payload_type_set_number ( pt,belle_sdp_mime_parameter_get_media_format ( mime_param ) );
		pt->clock_rate=belle_sdp_mime_parameter_get_rate ( mime_param );
		pt->mime_type=ms_strdup ( belle_sdp_mime_parameter_get_type ( mime_param ) );
		pt->channels=belle_sdp_mime_parameter_get_channel_count ( mime_param );
		payload_type_set_send_fmtp ( pt,belle_sdp_mime_parameter_get_parameters ( mime_param ) );
		payload_type_set_avpf_params(pt, avpf_params);
		cfg.payloads.push_back(pt);
		cfg.ptime=belle_sdp_mime_parameter_get_ptime ( mime_param );
		cfg.maxptime=belle_sdp_mime_parameter_get_max_ptime ( mime_param );
		ms_message ( "Found payload %s/%i fmtp=%s",pt->mime_type,pt->clock_rate,
						pt->send_fmtp ? pt->send_fmtp : "" );
	}
	if ( mime_params ) belle_sip_list_free_with_data ( mime_params,belle_sip_object_unref );
}

void SalStreamDescription::sdpParseMediaCryptoParameters(SalStreamConfiguration & cfg, const belle_sdp_media_description_t *media_desc) const {
	belle_sip_list_t *attribute_it;
	belle_sdp_attribute_t *attribute;

	cfg.crypto.clear();
	for ( attribute_it=belle_sdp_media_description_get_attributes ( media_desc )
						; attribute_it!=NULL;
			attribute_it=attribute_it->next ) {
		attribute=BELLE_SDP_ATTRIBUTE ( attribute_it->data );

		if ( keywordcmp ( "crypto",belle_sdp_attribute_get_name ( attribute ) ) ==0 && belle_sdp_attribute_get_value ( attribute ) !=NULL ) {
			const std::string cryptoAttrVal = belle_sdp_attribute_get_value ( attribute );
			const auto cryptoEl = SalStreamConfiguration::fillStrpCryptoAlgoFromString(cryptoAttrVal);
			if (cryptoEl.algo!=MS_CRYPTO_SUITE_INVALID){
				cfg.crypto.push_back(cryptoEl);
			}else{
				ms_warning ( "sdp has a strange a= line (%s)",belle_sdp_attribute_get_value ( attribute ) );
			}
		}
	}
	ms_message("Found: %0zu valid crypto lines", cfg.crypto.size());
}

void SalStreamDescription::sdpParseMediaIceParameters(SalStreamConfiguration & cfg, const belle_sdp_media_description_t *media_desc) {
	belle_sip_list_t *attribute_it;
	belle_sdp_attribute_t *attribute;
	const char *att_name;
	const char *value;

	for (attribute_it = belle_sdp_media_description_get_attributes(media_desc); attribute_it != NULL; attribute_it=attribute_it->next) {
		attribute=BELLE_SDP_ATTRIBUTE(attribute_it->data);
		att_name = belle_sdp_attribute_get_name(attribute);
		value = belle_sdp_attribute_get_value(attribute);

		char addr[SAL_MEDIA_DESCRIPTION_MAX_ICE_ADDR_LEN];
		if ((keywordcmp("candidate", att_name) == 0)
				&& (value != NULL)) {
			SalIceCandidate candidate;
			char raddr[SAL_MEDIA_DESCRIPTION_MAX_ICE_ADDR_LEN] = { 0 };
			char foundation[SAL_MEDIA_DESCRIPTION_MAX_ICE_ADDR_LEN] = { 0 };
			char type[SAL_MEDIA_DESCRIPTION_MAX_ICE_ADDR_LEN] = { 0 };
			char proto[4] = { 0 };
			int nb = sscanf(value, "%s %u %3s %u %s %d typ %s raddr %s rport %d",
				foundation, &candidate.componentID, proto, &candidate.priority, addr, &candidate.port,
				type, raddr, &candidate.rport);
			candidate.addr = addr;
			candidate.raddr = raddr;
			candidate.foundation = foundation;
			candidate.type = type;
			if (strcasecmp("udp",proto)==0 && ((nb == 7) || (nb == 9))) {
				ice_candidates.push_back(candidate);
			} else {
				ms_error("ice: Failed parsing a=candidate SDP attribute");
			}
		} else if ((keywordcmp("remote-candidates", att_name) == 0) && (value != NULL)) {
			SalIceRemoteCandidate candidate;
			unsigned int componentID;
			int offset;
			const char *ptr = value;
			const char *endptr = value + strlen(ptr);
			while (3 == sscanf(ptr, "%u %s %u%n", &componentID, addr, &candidate.port, &offset)) {
				candidate.addr = addr;
				if (componentID > 0) {
					SalIceRemoteCandidate remote_candidate;
					remote_candidate.addr = candidate.addr;
					remote_candidate.port = candidate.port;
					const unsigned int candidateIdx = componentID - 1;
					const unsigned int noCandidates = (unsigned int)ice_remote_candidates.size();
					if (candidateIdx >= noCandidates) {
						ice_remote_candidates.resize(componentID);
					}
					ice_remote_candidates[(std::vector<SalIceRemoteCandidate>::size_type)candidateIdx] = remote_candidate;
				}
				ptr += offset;
				if (ptr < endptr) {
					if (*ptr == ' ') ptr += 1;
				} else break;
			}
		} else if ((keywordcmp("ice-ufrag", att_name) == 0) && (value != NULL)) {
			ice_ufrag = L_C_TO_STRING(value);
		} else if ((keywordcmp("ice-pwd", att_name) == 0) && (value != NULL)) {
			ice_pwd = L_C_TO_STRING(value);
		} else if (keywordcmp("ice-mismatch", att_name) == 0) {
			ice_mismatch = true;
		}
	}
}

void SalStreamDescription::applyRtcpFbAttributeToPayload(SalStreamConfiguration & cfg, belle_sdp_rtcp_fb_attribute_t *fb_attribute, PayloadType *pt) {
	PayloadTypeAvpfParams avpf_params = payload_type_get_avpf_params(pt);
	switch (belle_sdp_rtcp_fb_attribute_get_type(fb_attribute)) {
		case BELLE_SDP_RTCP_FB_ACK:
			if (belle_sdp_rtcp_fb_attribute_get_param(fb_attribute) == BELLE_SDP_RTCP_FB_RPSI) {
				avpf_params.features |= PAYLOAD_TYPE_AVPF_RPSI;
			}
			break;
		case BELLE_SDP_RTCP_FB_NACK:
			switch (belle_sdp_rtcp_fb_attribute_get_param(fb_attribute)) {
				case BELLE_SDP_RTCP_FB_PLI:
					avpf_params.features |= PAYLOAD_TYPE_AVPF_PLI;
					break;
				case BELLE_SDP_RTCP_FB_SLI:
					avpf_params.features |= PAYLOAD_TYPE_AVPF_SLI;
					break;
				case BELLE_SDP_RTCP_FB_RPSI:
					/* Linphone uses positive feeback for RPSI. However first versions handling
					 * AVPF wrongly declared RPSI as negative feedback, so this is kept for compatibility
					 * with these versions but will probably be removed at some point in time. */
					avpf_params.features |= PAYLOAD_TYPE_AVPF_RPSI;
					avpf_params.rpsi_compatibility = true;
					break;
				case BELLE_SDP_RTCP_FB_NONE:
					cfg.rtcp_fb.generic_nack_enabled = true;
					break;
				default:
					break;
			}
			break;
		case BELLE_SDP_RTCP_FB_TRR_INT:
			avpf_params.trr_interval = belle_sdp_rtcp_fb_attribute_get_trr_int(fb_attribute);
			break;
		case BELLE_SDP_RTCP_FB_CCM:
			switch (belle_sdp_rtcp_fb_attribute_get_param(fb_attribute)) {
				case BELLE_SDP_RTCP_FB_FIR:
					avpf_params.features |= PAYLOAD_TYPE_AVPF_FIR;
					break;
				case BELLE_SDP_RTCP_FB_TMMBR:
					cfg.rtcp_fb.tmmbr_enabled = true;
					break;
				default:
					break;
			}
			break;
		default:
			break;
	}
	payload_type_set_avpf_params(pt, avpf_params);
}

void SalStreamDescription::addMidAttributesToSdp(const SalStreamConfiguration & cfg, belle_sdp_media_description_t *media_desc) const {
	if (cfg.mid.empty() == false){
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("mid", L_STRING_TO_C(cfg.mid)));
	}
	if (cfg.mid_rtp_ext_header_id){
		char *value = bctbx_strdup_printf("%i urn:ietf:params:rtp-hdrext:sdes:mid", cfg.mid_rtp_ext_header_id);
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("extmap", value));
		bctbx_free(value);
	}
	if (cfg.bundle_only){
		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("bundle-only", NULL));
	}
}

bool SalStreamDescription::isRtcpFbTrrIntTheSameForAllPayloads(const SalStreamConfiguration & cfg, uint16_t *trr_int) const {
	bool first = true;
	for (const auto & pt : cfg.payloads) {
		if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) {
			if (first == true) {
				*trr_int = payload_type_get_avpf_params(pt).trr_interval;
				first = false;
			} else if (payload_type_get_avpf_params(pt).trr_interval != *trr_int) {
				return false;
			}
		}
	}
	return true;
}

void SalStreamDescription::addRtcpFbAttributesToSdp(const SalStreamConfiguration & cfg, belle_sdp_media_description_t *media_desc) const {
	PayloadTypeAvpfParams avpf_params;
	bool general_trr_int;
	uint16_t trr_int = 0;

	general_trr_int = isRtcpFbTrrIntTheSameForAllPayloads(cfg, &trr_int);
	if (general_trr_int == true && trr_int != 0) {
		add_rtcp_fb_trr_int_attribute(media_desc, -1, trr_int);
	}
	if (cfg.rtcp_fb.generic_nack_enabled == TRUE) {
		add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_NONE);
	}
	if (cfg.rtcp_fb.tmmbr_enabled == TRUE) {
		add_rtcp_fb_ccm_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_TMMBR);
	}

	for (const auto & pt : cfg.payloads) {

		/* AVPF/SAVPF profile is used so enable AVPF for all payload types. */
		payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED);
		avpf_params = payload_type_get_avpf_params(pt);

		/* Add trr-int if not set generally. */
		if (general_trr_int != true && trr_int != 0) {
			add_rtcp_fb_trr_int_attribute(media_desc, (int8_t)payload_type_get_number(pt), avpf_params.trr_interval);
		}

		/* Add rtcp-fb attributes according to the AVPF features of the payload types. */
		if (avpf_params.features & PAYLOAD_TYPE_AVPF_PLI) {
			add_rtcp_fb_nack_attribute(media_desc, (int8_t)payload_type_get_number(pt), BELLE_SDP_RTCP_FB_PLI);
		}
		if (avpf_params.features & PAYLOAD_TYPE_AVPF_SLI) {
			add_rtcp_fb_nack_attribute(media_desc, (int8_t)payload_type_get_number(pt), BELLE_SDP_RTCP_FB_SLI);
		}
		if (avpf_params.features & PAYLOAD_TYPE_AVPF_RPSI) {
			if (avpf_params.rpsi_compatibility == TRUE) {
				add_rtcp_fb_nack_attribute(media_desc, (int8_t)payload_type_get_number(pt), BELLE_SDP_RTCP_FB_RPSI);
			} else {
				add_rtcp_fb_ack_attribute(media_desc, (int8_t)payload_type_get_number(pt), BELLE_SDP_RTCP_FB_RPSI);
			}
		}
		if (avpf_params.features & PAYLOAD_TYPE_AVPF_FIR) {
			add_rtcp_fb_ccm_attribute(media_desc, (int8_t)payload_type_get_number(pt), BELLE_SDP_RTCP_FB_FIR);
		}
	}
}

void SalStreamDescription::addIceCandidatesToSdp(const SalStreamConfiguration & cfg, belle_sdp_media_description_t *md) const {
	for (const auto & candidate : ice_candidates) {
		if ((candidate.addr.empty()) || (candidate.port == 0)) break;
		std::string iceCandidateValue = candidate.foundation + " " + std::to_string(candidate.componentID) + " UDP " + std::to_string(candidate.priority) + " " + candidate.addr.c_str() + " " + std::to_string(candidate.port) + " typ " + candidate.type;
		if (iceCandidateValue.size() > 1024) {
			ms_error("Cannot add ICE candidate attribute!");
			return;
		}
		if (!candidate.raddr.empty()) {
			iceCandidateValue += " raddr " + candidate.raddr + " rport " + std::to_string(candidate.rport);
			if (iceCandidateValue.size() > 1024) {
				ms_error("Cannot add ICE candidate attribute!");
				return;
			}
		}
		belle_sdp_media_description_add_attribute(md,belle_sdp_attribute_create("candidate",iceCandidateValue.c_str()));
	}
}

void SalStreamDescription::addIceRemoteCandidatesToSdp(const SalStreamConfiguration & cfg, belle_sdp_media_description_t *md) const {
	std::string iceRemoteCandidateValue;

	for (size_t i = 0; i < ice_remote_candidates.size(); i++) {
		const auto & candidate = ice_remote_candidates[i];
		if ((!candidate.addr.empty()) && (candidate.port != 0)) {
			iceRemoteCandidateValue += ((i > 0) ? " " : "") + std::to_string(static_cast<unsigned int>(i + 1)) + " " + candidate.addr + " " + std::to_string(candidate.port);
			
			if (iceRemoteCandidateValue.size() > 1024) {
				ms_error("Cannot add ICE remote-candidates attribute!");
				return;
			}
		}
	}
	if (!iceRemoteCandidateValue.empty()) belle_sdp_media_description_add_attribute(md,belle_sdp_attribute_create("remote-candidates",iceRemoteCandidateValue.c_str()));
}

const std::list<LinphoneMediaEncryption> & SalStreamDescription::getSupportedEncryptions() const {
	return supportedEncryption;
}

const PotentialCfgGraph::media_description_config::key_type & SalStreamDescription::getChosenConfigurationIndex() const {
	return cfgIndex;
}

const PotentialCfgGraph::media_description_config::key_type & SalStreamDescription::getActualConfigurationIndex() const {
	return SalStreamDescription::actualConfigurationIndex;
}

bool SalStreamDescription::hasConfigurationAtIndex(const PotentialCfgGraph::media_description_config::key_type & index) const {
	const auto & elCount = cfgs.count(index);
	return (elCount != 0);
}

const SalStreamConfiguration & SalStreamDescription::getConfigurationAtIndex(const PotentialCfgGraph::media_description_config::key_type & index) const {
	try {
		const auto & cfg = cfgs.at(index);
		return cfg;
	} catch (std::out_of_range&) {
		lDebug() << "Unable to find configuration at index " << index << " in the available configuration map";
		return Utils::getEmptyConstRefObject<SalStreamConfiguration>();
	}
}

const SalStreamConfiguration & SalStreamDescription::getActualConfiguration() const {
	return getConfigurationAtIndex(getActualConfigurationIndex());
}

const SalStreamConfiguration & SalStreamDescription::getChosenConfiguration() const {
	return getConfigurationAtIndex(getChosenConfigurationIndex());
}

void SalStreamDescription::setZrtpHash(const uint8_t enable, uint8_t* zrtphash) {
	if (enable) {
		memcpy(cfgs[getChosenConfigurationIndex()].zrtphash, zrtphash, sizeof(cfgs[getChosenConfigurationIndex()].zrtphash));
	}
	cfgs[getChosenConfigurationIndex()].haveZrtpHash = enable;
}
void SalStreamDescription::setDtls(const SalDtlsRole role, const std::string & fingerprint) {
	cfgs[getChosenConfigurationIndex()].dtls_role = role;
	cfgs[getChosenConfigurationIndex()].dtls_fingerprint = fingerprint;
}

void SalStreamDescription::setBundleOnly(const bool enable) {
	cfgs[getChosenConfigurationIndex()].bundle_only = enable;
}

bool SalStreamDescription::isBundleOnly() const {
	return getChosenConfiguration().isBundleOnly();
}

void SalStreamDescription::addActualConfiguration(const SalStreamConfiguration & cfg) {
	addConfigurationAtIndex(getActualConfigurationIndex(), cfg);
}

void SalStreamDescription::addConfigurationAtIndex(const PotentialCfgGraph::media_description_config::key_type & idx, const SalStreamConfiguration & cfg) {
	cfgs[idx] = cfg;
}

void SalStreamDescription::addTcap(const unsigned int & idx, const std::string & value) {
	tcaps[idx] = value;
}

const std::string & SalStreamDescription::getTcap(const unsigned int & idx) const {
	try {
		return tcaps.at(idx);
	} catch (std::out_of_range&) {
		lDebug() << "Unable to find transport capability at index " << idx;
		return Utils::getEmptyConstRefObject<std::string>();
	}
}

void SalStreamDescription::addAcap(const unsigned int & idx, const std::string & name, const std::string & value) {
	acaps[idx] = std::make_pair(name, value);
}

const SalStreamDescription::acap_t & SalStreamDescription::getAcap(const unsigned int & idx) const {
	try {
		return acaps.at(idx);
	} catch (std::out_of_range&) {
		lDebug() << "Unable to find attribute capability at index " << idx;
		return Utils::getEmptyConstRefObject<SalStreamDescription::acap_t>();
	}
}

const SalStreamDescription::acap_map_t & SalStreamDescription::getAcaps() const {
	return acaps;
}

const SalStreamDescription::tcap_map_t & SalStreamDescription::getTcaps() const {
	return tcaps;
}

unsigned int SalStreamDescription::getFreeCfgIdx() const {
	std::list<unsigned int> cfgIndexes;
	auto addToIndexList = [&cfgIndexes] (const auto & cfg) {
		cfgIndexes.push_back(cfg.first);
	};
	const auto & streamCfgs = getAllCfgs();
	std::for_each(streamCfgs.begin(), streamCfgs.end(), addToIndexList);

	return PotentialCfgGraph::getFreeIdx(cfgIndexes);
}

bool SalStreamDescription::getIceMismatch() const {
	return ice_mismatch;
}

const std::string & SalStreamDescription::getIceUfrag() const {
	return ice_ufrag;
}

const std::string & SalStreamDescription::getIcePwd() const {
	return ice_pwd;
}

const SalIceCandidate & SalStreamDescription::getIceCandidateAtIndex(const std::size_t & idx) const {
	try {
		return ice_candidates.at(idx);
	} catch (std::out_of_range&) {
		lError() << "Unable to Ice Candidate at index " << idx;
		return Utils::getEmptyConstRefObject<SalIceCandidate>();
	}
}

const SalIceRemoteCandidate & SalStreamDescription::getIceRemoteCandidateAtIndex(const std::size_t & idx) const {
	try {
		return ice_remote_candidates.at(idx);
	} catch (std::out_of_range&) {
		lError() << "Unable to Ice Remote Candidate at index " << idx;
		return Utils::getEmptyConstRefObject<SalIceRemoteCandidate>();
	}
}

bool SalStreamDescription::hasIceCandidates() const {
	return (!ice_candidates.empty());
}

bool SalStreamDescription::hasIceParams() const {
	// Return true if ice pwd and ufrag as well as candidates are defined
	return (!ice_ufrag.empty() && !ice_pwd.empty() && hasIceCandidates());
}
LINPHONE_END_NAMESPACE
