/*
 * Copyright (C) 2009  Barracuda Networks, Inc.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "jinglertp.h"

#include <stdio.h>
#include <stdlib.h>
#include <QtCrypto>
#include "iris/netnames.h"
#include "iris/turnclient.h"
#include "iris/udpportreserver.h"
#include "xmpp_client.h"

// TODO: reject offers that don't contain at least one of audio or video
// TODO: support candidate negotiations over the JingleRtpChannel thread
//   boundary, so we can change candidates after the stream is active

// scope values: 0 = local, 1 = link-local, 2 = private, 3 = public
static int getAddressScope(const QHostAddress &a)
{
	if(a.protocol() == QAbstractSocket::IPv6Protocol)
	{
		if(a == QHostAddress(QHostAddress::LocalHostIPv6))
			return 0;
		else if(XMPP::Ice176::isIPv6LinkLocalAddress(a))
			return 1;
	}
	else if(a.protocol() == QAbstractSocket::IPv4Protocol)
	{
		quint32 v4 = a.toIPv4Address();
		quint8 a0 = v4 >> 24;
		quint8 a1 = (v4 >> 16) & 0xff;
		if(a0 == 127)
			return 0;
		else if(a0 == 169 && a1 == 254)
			return 1;
		else if(a0 == 10)
			return 2;
		else if(a0 == 172 && a1 >= 16 && a1 <= 31)
			return 2;
		else if(a0 == 192 && a1 == 168)
			return 2;
	}

	return 3;
}

// -1 = a is higher priority, 1 = b is higher priority, 0 = equal
static int comparePriority(const QHostAddress &a, const QHostAddress &b)
{
	// prefer closer scope
	int a_scope = getAddressScope(a);
	int b_scope = getAddressScope(b);
	if(a_scope < b_scope)
		return -1;
	else if(a_scope > b_scope)
		return 1;

	// prefer ipv6
	if(a.protocol() == QAbstractSocket::IPv6Protocol && b.protocol() != QAbstractSocket::IPv6Protocol)
		return -1;
	else if(b.protocol() == QAbstractSocket::IPv6Protocol && a.protocol() != QAbstractSocket::IPv6Protocol)
		return 1;

	return 0;
}

static QList<QHostAddress> sortAddrs(const QList<QHostAddress> &in)
{
	QList<QHostAddress> out;

	foreach(const QHostAddress &a, in)
	{
		int at;
		for(at = 0; at < out.count(); ++at)
		{
			if(comparePriority(a, out[at]) < 0)
				break;
		}

		out.insert(at, a);
	}

	return out;
}

static QChar randomPrintableChar()
{
	// 0-25 = a-z
	// 26-51 = A-Z
	// 52-61 = 0-9

	uchar c = QCA::Random::randomChar() % 62;
	if(c <= 25)
		return 'a' + c;
	else if(c <= 51)
		return 'A' + (c - 26);
	else
		return '0' + (c - 52);
}

static QString randomCredential(int len)
{
	QString out;
	for(int n = 0; n < len; ++n)
		out += randomPrintableChar();
	return out;
}

// resolve external address and stun server
// TODO: resolve hosts and start ice engine simultaneously
// FIXME: when/if our ICE engine supports adding these dynamically, we should
//   not have the lookups block on each other
class Resolver : public QObject
{
	Q_OBJECT

private:
	XMPP::NameResolver dnsA;
	XMPP::NameResolver dnsB;
	XMPP::NameResolver dnsC;
	XMPP::NameResolver dnsD;
	QString extHost;
	QString stunBindHost, stunRelayUdpHost, stunRelayTcpHost;
	bool extDone;
	bool stunBindDone;
	bool stunRelayUdpDone;
	bool stunRelayTcpDone;

public:
	QHostAddress extAddr;
	QHostAddress stunBindAddr, stunRelayUdpAddr, stunRelayTcpAddr;

	Resolver(QObject *parent = 0) :
		QObject(parent),
		dnsA(parent),
		dnsB(parent),
		dnsC(parent),
		dnsD(parent)
	{
		connect(&dnsA, SIGNAL(resultsReady(const QList<XMPP::NameRecord> &)), SLOT(dns_resultsReady(const QList<XMPP::NameRecord> &)));
		connect(&dnsA, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error)));

		connect(&dnsB, SIGNAL(resultsReady(const QList<XMPP::NameRecord> &)), SLOT(dns_resultsReady(const QList<XMPP::NameRecord> &)));
		connect(&dnsB, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error)));

		connect(&dnsC, SIGNAL(resultsReady(const QList<XMPP::NameRecord> &)), SLOT(dns_resultsReady(const QList<XMPP::NameRecord> &)));
		connect(&dnsC, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error)));

		connect(&dnsD, SIGNAL(resultsReady(const QList<XMPP::NameRecord> &)), SLOT(dns_resultsReady(const QList<XMPP::NameRecord> &)));
		connect(&dnsD, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error)));
	}

	void start(const QString &_extHost, const QString &_stunBindHost, const QString &_stunRelayUdpHost, const QString &_stunRelayTcpHost)
	{
		extHost = _extHost;
		stunBindHost = _stunBindHost;
		stunRelayUdpHost = _stunRelayUdpHost;
		stunRelayTcpHost = _stunRelayTcpHost;

		if(!extHost.isEmpty())
		{
			extDone = false;
			dnsA.start(extHost.toLatin1());
		}
		else
			extDone = true;

		if(!stunBindHost.isEmpty())
		{
			stunBindDone = false;
			dnsB.start(stunBindHost.toLatin1());
		}
		else
			stunBindDone = true;

		if(!stunRelayUdpHost.isEmpty())
		{
			stunRelayUdpDone = false;
			dnsC.start(stunRelayUdpHost.toLatin1());
		}
		else
			stunRelayUdpDone = true;

		if(!stunRelayTcpHost.isEmpty())
		{
			stunRelayTcpDone = false;
			dnsD.start(stunRelayTcpHost.toLatin1());
		}
		else
			stunRelayTcpDone = true;

		if(extDone && stunBindDone && stunRelayUdpDone && stunRelayTcpDone)
			QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
	}

signals:
	void finished();

private slots:
	void dns_resultsReady(const QList<XMPP::NameRecord> &results)
	{
		XMPP::NameResolver *dns = (XMPP::NameResolver *)sender();

		// FIXME: support more than one address?
		QHostAddress addr = results.first().address();

		if(dns == &dnsA)
		{
			extAddr = addr;
			extDone = true;
			tryFinish();
		}
		else if(dns == &dnsB)
		{
			stunBindAddr = addr;
			stunBindDone = true;
			tryFinish();
		}
		else if(dns == &dnsC)
		{
			stunRelayUdpAddr = addr;
			stunRelayUdpDone = true;
			tryFinish();
		}
		else // dnsD
		{
			stunRelayTcpAddr = addr;
			stunRelayTcpDone = true;
			tryFinish();
		}
	}

	void dns_error(XMPP::NameResolver::Error e)
	{
		Q_UNUSED(e);

		XMPP::NameResolver *dns = (XMPP::NameResolver *)sender();

		if(dns == &dnsA)
		{
			extDone = true;
			tryFinish();
		}
		else if(dns == &dnsB)
		{
			stunBindDone = true;
			tryFinish();
		}
		else if(dns == &dnsC)
		{
			stunRelayUdpDone = true;
			tryFinish();
		}
		else // dnsD
		{
			stunRelayTcpDone = true;
			tryFinish();
		}
	}

private:
	void tryFinish()
	{
		if(extDone && stunBindDone && stunRelayUdpDone && stunRelayTcpDone)
			emit finished();
	}
};

class IceStopper : public QObject
{
	Q_OBJECT

public:
	QTimer t;
	XMPP::UdpPortReserver *portReserver;
	QList<XMPP::Ice176*> left;

	IceStopper(QObject *parent = 0) :
		QObject(parent),
		t(this),
		portReserver(0)
	{
		connect(&t, SIGNAL(timeout()), SLOT(t_timeout()));
		t.setSingleShot(true);
	}

	~IceStopper()
	{
		qDeleteAll(left);
		delete portReserver;
		printf("IceStopper done\n");
	}

	void start(XMPP::UdpPortReserver *_portReserver, const QList<XMPP::Ice176*> iceList)
	{
		if(_portReserver)
		{
			portReserver = _portReserver;
			portReserver->setParent(this);
		}
		left = iceList;

		foreach(XMPP::Ice176 *ice, left)
		{
			ice->setParent(this);

			// TODO: error() also?
			connect(ice, SIGNAL(stopped()), SLOT(ice_stopped()));
			connect(ice, SIGNAL(error(XMPP::Ice176::Error)), SLOT(ice_error(XMPP::Ice176::Error)));
			ice->stop();
		}

		t.start(3000);
	}

private slots:
	void ice_stopped()
	{
		XMPP::Ice176 *ice = (XMPP::Ice176 *)sender();
		ice->disconnect(this);
		ice->setParent(0);
		ice->deleteLater();
		left.removeAll(ice);
		if(left.isEmpty())
			deleteLater();
	}

	void ice_error(XMPP::Ice176::Error e)
	{
		Q_UNUSED(e);

		ice_stopped();
	}

	void t_timeout()
	{
		deleteLater();
	}
};

//----------------------------------------------------------------------------
// JingleRtp
//----------------------------------------------------------------------------
class JingleRtpChannelPrivate : public QObject
{
	Q_OBJECT

public:
	JingleRtpChannel *q;

	QMutex m;
	XMPP::UdpPortReserver *portReserver;
	XMPP::Ice176 *iceA;
	XMPP::Ice176 *iceV;
	QTimer *rtpActivityTimer;
	QList<JingleRtp::RtpPacket> in;

	JingleRtpChannelPrivate(JingleRtpChannel *_q);
	~JingleRtpChannelPrivate();

	void setIceObjects(XMPP::UdpPortReserver *_portReserver, XMPP::Ice176 *_iceA, XMPP::Ice176 *_iceV);
	void restartRtpActivityTimer();

private slots:
	void start();
	void ice_readyRead(int componentIndex);
	void ice_datagramsWritten(int componentIndex, int count);
	void rtpActivity_timeout();
};

class JingleRtpManagerPrivate : public QObject
{
	Q_OBJECT

public:
	JingleRtpManager *q;

	XMPP::Client *client;
	QHostAddress selfAddr;
	QString extHost;
	QString stunBindHost;
	int stunBindPort;
	QString stunRelayUdpHost;
	int stunRelayUdpPort;
	QString stunRelayUdpUser;
	QString stunRelayUdpPass;
	QString stunRelayTcpHost;
	int stunRelayTcpPort;
	QString stunRelayTcpUser;
	QString stunRelayTcpPass;
	XMPP::TurnClient::Proxy stunProxy;
	int basePort;
	QList<JingleRtp*> sessions;
	QList<JingleRtp*> pending;
	JT_PushJingleRtp *push_task;

	JingleRtpManagerPrivate(XMPP::Client *_client, JingleRtpManager *_q);
	~JingleRtpManagerPrivate();

	QString createSid(const XMPP::Jid &peer) const;

	void unlink(JingleRtp *sess);

private slots:
	void push_task_incomingRequest(const XMPP::Jid &from, const QString &iq_id, const JingleRtpEnvelope &envelope);
};

class JingleRtpPrivate : public QObject
{
	Q_OBJECT

public:
	JingleRtp *q;

	JingleRtpManagerPrivate *manager;
	bool incoming;
	XMPP::Jid peer;
	QString sid;

	int types;
	QList<JingleRtpPayloadType> localAudioPayloadTypes;
	QList<JingleRtpPayloadType> localVideoPayloadTypes;
	QList<JingleRtpPayloadType> remoteAudioPayloadTypes;
	QList<JingleRtpPayloadType> remoteVideoPayloadTypes;
	int localMaximumBitrate;
	int remoteMaximumBitrate;
	QString audioName;
	QString videoName;

	QString init_iq_id;
	JT_JingleRtp *jt;

	Resolver resolver;
	QHostAddress extAddr;
	QHostAddress stunBindAddr, stunRelayUdpAddr, stunRelayTcpAddr;
	int stunBindPort, stunRelayUdpPort, stunRelayTcpPort;
	XMPP::Ice176 *iceA;
	XMPP::Ice176 *iceV;
	JingleRtpChannel *rtpChannel;

	class IceStatus
	{
	public:
		bool started;

		QString remoteUfrag;
		QString remotePassword;

		// for queuing up candidates before using them
		QList<XMPP::Ice176::Candidate> localCandidates;
		QList<XMPP::Ice176::Candidate> remoteCandidates;

		QVector<bool> channelsReady;
	};
	IceStatus iceA_status;
	IceStatus iceV_status;

	bool local_media_ready;
	bool prov_accepted;
	bool ice_connected;
	bool session_accepted;
	bool session_activated;
	QTimer *handshakeTimer;
	JingleRtp::Error errorCode;
	XMPP::UdpPortReserver *portReserver;

	JingleRtpPrivate(JingleRtp *_q) :
		QObject(_q),
		q(_q),
		manager(0),
		localMaximumBitrate(-1),
		remoteMaximumBitrate(-1),
		jt(0),
		resolver(this),
		iceA(0),
		iceV(0),
		local_media_ready(false),
		prov_accepted(false),
		ice_connected(false),
		session_accepted(false),
		session_activated(false),
		portReserver(0)
	{
		connect(&resolver, SIGNAL(finished()), SLOT(resolver_finished()));

		handshakeTimer = new QTimer(this);
		connect(handshakeTimer, SIGNAL(timeout()), SLOT(handshake_timeout()));
		handshakeTimer->setSingleShot(true);

		rtpChannel = new JingleRtpChannel;
	}

	~JingleRtpPrivate()
	{
		cleanup();
		manager->unlink(q);

		handshakeTimer->setParent(0);
		handshakeTimer->disconnect(this);
		handshakeTimer->deleteLater();

		delete rtpChannel;
	}

	void startOutgoing()
	{
		local_media_ready = true;

		types = 0;
		if(!localAudioPayloadTypes.isEmpty())
		{
			printf("there are audio payload types\n");
			types |= JingleRtp::Audio;
		}
		if(!localVideoPayloadTypes.isEmpty())
		{
			printf("there are video payload types\n");
			types |= JingleRtp::Video;
		}

		printf("types=%d\n", types);
		resolver.start(manager->extHost, manager->stunBindHost, manager->stunRelayUdpHost, manager->stunRelayTcpHost);
	}

	void accept(int _types)
	{
		types = _types;

		// TODO: cancel away whatever media type is not used

		resolver.start(manager->extHost, manager->stunBindHost, manager->stunRelayUdpHost, manager->stunRelayTcpHost);
	}

	void reject()
	{
		if(!incoming)
		{
			bool ok = true;
			if((types & JingleRtp::Audio) && !iceA_status.started)
				ok = false;
			if((types & JingleRtp::Video) && !iceV_status.started)
				ok = false;

			// we haven't even sent session-initiate
			if(!ok)
			{
				cleanup();
				return;
			}
		}
		else
		{
			// send iq-result if we haven't done so yet
			if(!prov_accepted)
			{
				prov_accepted = true;
				manager->push_task->respondSuccess(peer, init_iq_id);
			}
		}

		JingleRtpEnvelope envelope;
		envelope.action = "session-terminate";
		envelope.sid = sid;
		envelope.reason.condition = JingleRtpReason::Gone;

		JT_JingleRtp *task = new JT_JingleRtp(manager->client->rootTask());
		task->request(peer, envelope);
		task->go(true);

		cleanup();
	}

	void localMediaUpdate()
	{
		local_media_ready = true;

		tryAccept();
		tryActivated();
	}

	// called by manager when request is received, including
	//   session-initiate.
	// note: manager will never send session-initiate twice.
	bool incomingRequest(const QString &iq_id, const JingleRtpEnvelope &envelope)
	{
		// TODO: jingle has a lot of fields, and we kind of skip over
		//   most of them just to grab what we need.  perhaps in the
		//   future we could do more integrity checking.

		if(envelope.action == "session-initiate")
		{
			// initially flag both types, so we don't drop any
			//   transport-info before we accept (at which point
			//   we specify what types we actually want)
			types = JingleRtp::Audio | JingleRtp::Video;

			init_iq_id = iq_id;

			const JingleRtpContent *audioContent = 0;
			const JingleRtpContent *videoContent = 0;

			// find content
			foreach(const JingleRtpContent &c, envelope.contentList)
			{
				if((types & JingleRtp::Audio) && c.desc.media == "audio" && !audioContent)
				{
					audioContent = &c;
					break;
				}
				else if((types & JingleRtp::Video) && c.desc.media == "video" && !videoContent)
				{
					videoContent = &c;
					break;
				}
			}

			if(audioContent)
			{
				audioName = audioContent->name;
				remoteAudioPayloadTypes = audioContent->desc.payloadTypes;
				if(!audioContent->trans.user.isEmpty())
				{
					iceA_status.remoteUfrag = audioContent->trans.user;
					iceA_status.remotePassword = audioContent->trans.pass;
				}
				iceA_status.remoteCandidates += audioContent->trans.candidates;
			}
			if(videoContent)
			{
				videoName = videoContent->name;
				remoteVideoPayloadTypes = videoContent->desc.payloadTypes;
				if(!videoContent->trans.user.isEmpty())
				{
					iceV_status.remoteUfrag = videoContent->trans.user;
					iceV_status.remotePassword = videoContent->trans.pass;
				}
				iceV_status.remoteCandidates += videoContent->trans.candidates;
			}

			// must offer at least one audio or video payload
			if(remoteAudioPayloadTypes.isEmpty() && remoteVideoPayloadTypes.isEmpty())
				return false;
		}
		else if(envelope.action == "session-accept" && !incoming)
		{
			manager->push_task->respondSuccess(peer, iq_id);

			const JingleRtpContent *audioContent = 0;
			const JingleRtpContent *videoContent = 0;

			// find content
			foreach(const JingleRtpContent &c, envelope.contentList)
			{
				if((types & JingleRtp::Audio) && c.desc.media == "audio" && c.name == audioName && !audioContent)
				{
					audioContent = &c;
					break;
				}
				else if((types & JingleRtp::Video) && c.desc.media == "video" && c.name == videoName && !videoContent)
				{
					videoContent = &c;
					break;
				}
			}

			// we support audio, peer doesn't
			if((types & JingleRtp::Audio) && !audioContent)
				types &= ~JingleRtp::Audio;

			// we support video, peer doesn't
			if((types & JingleRtp::Video) && !videoContent)
				types &= ~JingleRtp::Video;

			if(types == 0)
			{
				reject();
				emit q->rejected();
				return false;
			}

			if(audioContent)
			{
				remoteAudioPayloadTypes = audioContent->desc.payloadTypes;
				if(!audioContent->trans.user.isEmpty())
				{
					iceA_status.remoteUfrag = audioContent->trans.user;
					iceA_status.remotePassword = audioContent->trans.pass;
				}
				iceA_status.remoteCandidates += audioContent->trans.candidates;
			}
			if(videoContent)
			{
				remoteVideoPayloadTypes = videoContent->desc.payloadTypes;
				if(!videoContent->trans.user.isEmpty())
				{
					iceV_status.remoteUfrag = videoContent->trans.user;
					iceV_status.remotePassword = videoContent->trans.pass;
				}
				iceV_status.remoteCandidates += videoContent->trans.candidates;
			}

			restartHandshakeTimer();

			flushRemoteCandidates();

			session_accepted = true;
			QMetaObject::invokeMethod(this, "after_session_accept", Qt::QueuedConnection);
			emit q->remoteMediaUpdated();
		}
		else if(envelope.action == "session-terminate")
		{
			manager->push_task->respondSuccess(peer, iq_id);

			cleanup();
			emit q->rejected();
		}
		else if(envelope.action == "transport-info")
		{
			manager->push_task->respondSuccess(peer, iq_id);

			const JingleRtpContent *audioContent = 0;
			const JingleRtpContent *videoContent = 0;

			// find content
			foreach(const JingleRtpContent &c, envelope.contentList)
			{
				if((types & JingleRtp::Audio) && c.name == audioName && !audioContent)
				{
					audioContent = &c;
					break;
				}
				else if((types & JingleRtp::Video) && c.name == videoName && !videoContent)
				{
					videoContent = &c;
					break;
				}
			}

			if(audioContent)
			{
				if(!audioContent->trans.user.isEmpty())
				{
					iceA_status.remoteUfrag = audioContent->trans.user;
					iceA_status.remotePassword = audioContent->trans.pass;
				}

				printf("audio candidates=%d\n", audioContent->trans.candidates.count());
				iceA_status.remoteCandidates += audioContent->trans.candidates;
			}
			if(videoContent)
			{
				if(!videoContent->trans.user.isEmpty())
				{
					iceV_status.remoteUfrag = videoContent->trans.user;
					iceV_status.remotePassword = videoContent->trans.pass;
				}

				printf("video candidates=%d\n", videoContent->trans.candidates.count());
				iceV_status.remoteCandidates += videoContent->trans.candidates;
			}

			// don't process the candidates unless our ICE engine
			//   is started
			if(prov_accepted)
				flushRemoteCandidates();
		}
		else
		{
			manager->push_task->respondError(peer, iq_id, 400, QString());
			return false;
		}

		return true;
	}

private:
	void cleanup()
	{
		resolver.disconnect(this);
		handshakeTimer->stop();

		delete jt;
		jt = 0;

		if(portReserver)
		{
			portReserver->setParent(0);

			QList<XMPP::Ice176*> list;

			if(iceA)
			{
				iceA->disconnect(this);
				iceA->setParent(0);
				list += iceA;
				iceA = 0;
			}

			if(iceV)
			{
				iceV->disconnect(this);
				iceV->setParent(0);
				list += iceV;
				iceV = 0;
			}

			// pass ownership of portReserver, iceA, and iceV
			IceStopper *iceStopper = new IceStopper;
			iceStopper->start(portReserver, list);

			portReserver = 0;
		}
		else
		{
			if(iceA)
			{
				iceA->disconnect(this);
				iceA->setParent(0);
				iceA->deleteLater();
				iceA = 0;
			}

			if(iceV)
			{
				iceV->disconnect(this);
				iceV->setParent(0);
				iceV->deleteLater();
				iceV = 0;
			}

			delete portReserver;
			portReserver = 0;
		}

		// prevent delivery of events by manager
		peer = XMPP::Jid();
		sid.clear();
	}

	void start_ice()
	{
		stunBindPort = manager->stunBindPort;
		stunRelayUdpPort = manager->stunRelayUdpPort;
		stunRelayTcpPort = manager->stunRelayTcpPort;
		if(!stunBindAddr.isNull() && stunBindPort > 0)
			printf("STUN service: %s;%d\n", qPrintable(stunBindAddr.toString()), stunBindPort);
		if(!stunRelayUdpAddr.isNull() && stunRelayUdpPort > 0 && !manager->stunRelayUdpUser.isEmpty())
			printf("TURN w/ UDP service: %s;%d\n", qPrintable(stunRelayUdpAddr.toString()), stunRelayUdpPort);
		if(!stunRelayTcpAddr.isNull() && stunRelayTcpPort > 0 && !manager->stunRelayTcpUser.isEmpty())
			printf("TURN w/ TCP service: %s;%d\n", qPrintable(stunRelayTcpAddr.toString()), stunRelayTcpPort);

		QList<QHostAddress> listenAddrs;
		foreach(const QNetworkInterface &ni, QNetworkInterface::allInterfaces())
		{
			QList<QNetworkAddressEntry> entries = ni.addressEntries();
			foreach(const QNetworkAddressEntry &na, entries)
			{
				QHostAddress h = na.ip();

				// skip localhost
				if(getAddressScope(h) == 0)
					continue;

				// don't put the same address in twice.
				//   this also means that if there are
				//   two link-local ipv6 interfaces
				//   with the exact same address, we
				//   only use the first one
				if(listenAddrs.contains(h))
					continue;

				if(h.protocol() == QAbstractSocket::IPv6Protocol && XMPP::Ice176::isIPv6LinkLocalAddress(h))
					h.setScopeId(ni.name());
				listenAddrs += h;
			}
		}

		listenAddrs = sortAddrs(listenAddrs);

		QList<XMPP::Ice176::LocalAddress> localAddrs;

		QStringList strList;
		foreach(const QHostAddress &h, listenAddrs)
		{
			XMPP::Ice176::LocalAddress addr;
			addr.addr = h;
			localAddrs += addr;
			strList += h.toString();
		}

		if(manager->basePort != -1)
		{
			portReserver = new XMPP::UdpPortReserver(this);
			portReserver->setAddresses(listenAddrs);
			portReserver->setPorts(manager->basePort, 4);
		}

		if(!strList.isEmpty())
		{
			printf("Host addresses:\n");
			foreach(const QString &s, strList)
				printf("  %s\n", qPrintable(s));
		}

		if(types & JingleRtp::Audio)
		{
			iceA = new XMPP::Ice176(this);
			setup_ice(iceA, localAddrs);

			iceA_status.started = false;
			iceA_status.channelsReady.resize(2);
			iceA_status.channelsReady[0] = false;
			iceA_status.channelsReady[1] = false;
		}

		if(types & JingleRtp::Video)
		{
			iceV = new XMPP::Ice176(this);
			setup_ice(iceV, localAddrs);

			iceV_status.started = false;
			iceV_status.channelsReady.resize(2);
			iceV_status.channelsReady[0] = false;
			iceV_status.channelsReady[1] = false;
		}

		XMPP::Ice176::Mode m;
		if(!incoming)
			m = XMPP::Ice176::Initiator;
		else
			m = XMPP::Ice176::Responder;

		if(iceA)
		{
			printf("starting ice for audio\n");
			iceA->start(m);
		}
		if(iceV)
		{
			printf("starting ice for video\n");
			iceV->start(m);
		}
	}

	void setup_ice(XMPP::Ice176 *ice, const QList<XMPP::Ice176::LocalAddress> &localAddrs)
	{
		connect(ice, SIGNAL(started()), SLOT(ice_started()));
		connect(ice, SIGNAL(error(XMPP::Ice176::Error)), SLOT(ice_error(XMPP::Ice176::Error)));
		connect(ice, SIGNAL(localCandidatesReady(const QList<XMPP::Ice176::Candidate> &)), SLOT(ice_localCandidatesReady(const QList<XMPP::Ice176::Candidate> &)));
		connect(ice, SIGNAL(componentReady(int)), SLOT(ice_componentReady(int)), Qt::QueuedConnection); // signal is not DOR-SS

		ice->setProxy(manager->stunProxy);
		if(portReserver)
			ice->setPortReserver(portReserver);

		//QList<XMPP::Ice176::LocalAddress> localAddrs;
		//XMPP::Ice176::LocalAddress addr;

		// FIXME: the following is not true, a local address is not
		//   required, for example if you use TURN with TCP only

		// a local address is required to use ice.  however, if
		//   we don't have a local address, we won't handle it as
		//   an error here.  instead, we'll start Ice176 anyway,
		//   which should immediately error back at us.
		/*if(manager->selfAddr.isNull())
		{
			printf("no self address to use.  this will fail.\n");
			return;
		}

		addr.addr = manager->selfAddr;
		localAddrs += addr;*/
		ice->setLocalAddresses(localAddrs);

		// if an external address is manually provided, then apply
		//   it only to the selfAddr.  FIXME: maybe we should apply
		//   it to all local addresses?
		if(!extAddr.isNull())
		{
			QList<XMPP::Ice176::ExternalAddress> extAddrs;
			/*XMPP::Ice176::ExternalAddress eaddr;
			eaddr.base = addr;
			eaddr.addr = extAddr;
			extAddrs += eaddr;*/
			foreach(const XMPP::Ice176::LocalAddress &la, localAddrs)
			{
				XMPP::Ice176::ExternalAddress ea;
				ea.base = la;
				ea.addr = extAddr;
				extAddrs += ea;
			}
			ice->setExternalAddresses(extAddrs);
		}

		if(!stunBindAddr.isNull() && stunBindPort > 0)
			ice->setStunBindService(stunBindAddr, stunBindPort);
		if(!stunRelayUdpAddr.isNull() && !manager->stunRelayUdpUser.isEmpty())
			ice->setStunRelayUdpService(stunRelayUdpAddr, stunRelayUdpPort, manager->stunRelayUdpUser, manager->stunRelayUdpPass.toUtf8());
		if(!stunRelayTcpAddr.isNull() && !manager->stunRelayTcpUser.isEmpty())
			ice->setStunRelayTcpService(stunRelayTcpAddr, stunRelayTcpPort, manager->stunRelayTcpUser, manager->stunRelayTcpPass.toUtf8());

		// RTP+RTCP
		ice->setComponentCount(2);

		ice->setLocalCandidateTrickle(true);
	}

	// called when all ICE objects are started
	void after_ice_started()
	{
		printf("after_ice_started\n");

		// for outbound, send the session-initiate
		if(!incoming)
		{
			sid = manager->createSid(peer);

			JingleRtpEnvelope envelope;
			envelope.action = "session-initiate";
			envelope.initiator = manager->client->jid().full();
			envelope.sid = sid;

			if(types & JingleRtp::Audio)
			{
				audioName = "A";

				JingleRtpContent content;
				content.creator = "initiator";
				content.name = audioName;
				content.senders = "both";

				content.desc.media = "audio";
				content.desc.payloadTypes = localAudioPayloadTypes;
				content.trans.user = iceA->localUfrag();
				content.trans.pass = iceA->localPassword();

				envelope.contentList += content;
			}

			if(types & JingleRtp::Video)
			{
				videoName = "V";

				JingleRtpContent content;
				content.creator = "initiator";
				content.name = videoName;
				content.senders = "both";

				content.desc.media = "video";
				content.desc.payloadTypes = localVideoPayloadTypes;
				content.trans.user = iceV->localUfrag();
				content.trans.pass = iceV->localPassword();

				envelope.contentList += content;
			}

			jt = new JT_JingleRtp(manager->client->rootTask());
			connect(jt, SIGNAL(finished()), SLOT(task_finished()));
			jt->request(peer, envelope);
			jt->go(true);
		}
		else
		{
			restartHandshakeTimer();

			prov_accepted = true;
			manager->push_task->respondSuccess(peer, init_iq_id);

			flushRemoteCandidates();
		}
	}

	// received iq-result to session-initiate
	void prov_accept()
	{
		prov_accepted = true;

		flushLocalCandidates();
	}

	// received iq-error to session-initiate
	void prov_reject()
	{
		cleanup();
		emit q->rejected();
	}

	void flushLocalCandidates()
	{
		printf("flushing local candidates\n");

		QList<JingleRtpContent> contentList;

		// according to xep-166, creator is always whoever added
		//   the content type, which in our case is always the
		//   initiator

		if((types & JingleRtp::Audio) && !iceA_status.localCandidates.isEmpty())
		{
			JingleRtpContent content;
			//if(!incoming)
				content.creator = "initiator";
			//else
			//	content.creator = "responder";
			content.name = audioName;

			content.trans.user = iceA->localUfrag();
			content.trans.pass = iceA->localPassword();
			content.trans.candidates = iceA_status.localCandidates;
			iceA_status.localCandidates.clear();

			contentList += content;
		}

		if((types & JingleRtp::Video) && !iceV_status.localCandidates.isEmpty())
		{
			JingleRtpContent content;
			//if(!incoming)
				content.creator = "initiator";
			//else
			//	content.creator = "responder";
			content.name = videoName;

			content.trans.user = iceV->localUfrag();
			content.trans.pass = iceV->localPassword();
			content.trans.candidates = iceV_status.localCandidates;
			iceV_status.localCandidates.clear();

			contentList += content;
		}

		if(!contentList.isEmpty())
		{
			JingleRtpEnvelope envelope;
			envelope.action = "transport-info";
			envelope.sid = sid;
			envelope.contentList = contentList;

			JT_JingleRtp *task = new JT_JingleRtp(manager->client->rootTask());
			task->request(peer, envelope);
			task->go(true);
		}
	}

	void flushRemoteCandidates()
	{
		// FIXME: currently, new candidates are ignored after the
		//   session is activated (iceA/iceV are passed to
		//   JingleRtpChannel and our local pointers are nulled).
		//   unfortunately this means we can't upgrade to better
		//   candidates on the fly.

		if(types & JingleRtp::Audio && iceA)
		{
			iceA->setPeerUfrag(iceA_status.remoteUfrag);
			iceA->setPeerPassword(iceA_status.remotePassword);
			if(!iceA_status.remoteCandidates.isEmpty())
			{
				iceA->addRemoteCandidates(iceA_status.remoteCandidates);
				iceA_status.remoteCandidates.clear();
			}
		}

		if(types & JingleRtp::Video && iceV)
		{
			iceV->setPeerUfrag(iceV_status.remoteUfrag);
			iceV->setPeerPassword(iceV_status.remotePassword);
			if(!iceV_status.remoteCandidates.isEmpty())
			{
				iceV->addRemoteCandidates(iceV_status.remoteCandidates);
				iceV_status.remoteCandidates.clear();
			}
		}
	}

	// called when all ICE components are established
	void after_ice_connected()
	{
		if(incoming)
			tryAccept();

		tryActivated();
	}

	void tryAccept()
	{
		if(local_media_ready && ice_connected && !session_accepted)
		{
			JingleRtpEnvelope envelope;
			envelope.action = "session-accept";
			envelope.responder = manager->client->jid().full();
			envelope.sid = sid;

			if(types & JingleRtp::Audio)
			{
				JingleRtpContent content;
				content.creator = "initiator";
				content.name = audioName;
				content.senders = "both";

				content.desc.media = "audio";
				content.desc.payloadTypes = localAudioPayloadTypes;
				content.trans.user = iceA->localUfrag();
				content.trans.pass = iceA->localPassword();

				envelope.contentList += content;
			}

			if(types & JingleRtp::Video)
			{
				JingleRtpContent content;
				content.creator = "initiator";
				content.name = videoName;
				content.senders = "both";

				content.desc.media = "video";
				content.desc.payloadTypes = localVideoPayloadTypes;
				content.trans.user = iceV->localUfrag();
				content.trans.pass = iceV->localPassword();

				envelope.contentList += content;
			}

			session_accepted = true;

			JT_JingleRtp *task = new JT_JingleRtp(manager->client->rootTask());
			task->request(peer, envelope);
			task->go(true);
		}
	}

	void tryActivated()
	{
		if(session_accepted && ice_connected)
		{
			if(session_activated)
			{
				printf("warning: attempting to activate an already active session\n");
				return;
			}

			printf("activating!\n");
			session_activated = true;
			handshakeTimer->stop();

			if(portReserver)
				portReserver->setParent(0);

			if(iceA)
			{
				iceA->disconnect(this);
				iceA->setParent(0);
			}
			if(iceV)
			{
				iceV->disconnect(this);
				iceV->setParent(0);
			}

			rtpChannel->d->setIceObjects(portReserver, iceA, iceV);

			portReserver = 0;
			iceA = 0;
			iceV = 0;

			emit q->activated();
		}
	}

	void restartHandshakeTimer()
	{
		// there better be some activity in 10 seconds
		handshakeTimer->start(10000);
	}

private slots:
	void resolver_finished()
	{
		extAddr = resolver.extAddr;
		stunBindAddr = resolver.stunBindAddr;
		stunRelayUdpAddr = resolver.stunRelayUdpAddr;
		stunRelayTcpAddr = resolver.stunRelayTcpAddr;

		printf("resolver finished\n");

		start_ice();
	}

	void handshake_timeout()
	{
		reject();
		errorCode = JingleRtp::ErrorTimeout;
		emit q->error();
	}

	void ice_started()
	{
		XMPP::Ice176 *ice = (XMPP::Ice176 *)sender();

		printf("ice_started\n");

		if(ice == iceA)
		{
			iceA_status.started = true;

			// audio rtp/rtcp expected to have small packets
			iceA->flagComponentAsLowOverhead(0);
			iceA->flagComponentAsLowOverhead(1);
		}
		else // iceV
		{
			iceV_status.started = true;

			// video rtcp expected to have small packets
			iceV->flagComponentAsLowOverhead(1);
		}

		bool ok = true;
		if((types & JingleRtp::Audio) && !iceA_status.started)
			ok = false;
		if((types & JingleRtp::Video) && !iceV_status.started)
			ok = false;

		if(ok)
			after_ice_started();
	}

	void ice_error(XMPP::Ice176::Error e)
	{
		Q_UNUSED(e);

		errorCode = JingleRtp::ErrorICE;
		emit q->error();
	}

	void ice_localCandidatesReady(const QList<XMPP::Ice176::Candidate> &list)
	{
		XMPP::Ice176 *ice = (XMPP::Ice176 *)sender();

		if(ice == iceA)
			iceA_status.localCandidates += list;
		else // iceV
			iceV_status.localCandidates += list;

		printf("local candidate ready\n");

		if(prov_accepted)
			flushLocalCandidates();
	}

	void ice_componentReady(int index)
	{
		XMPP::Ice176 *ice = (XMPP::Ice176 *)sender();

		if(ice == iceA)
		{
			Q_ASSERT(!iceA_status.channelsReady[index]);
			iceA_status.channelsReady[index] = true;
		}
		else // iceV
		{
			Q_ASSERT(!iceV_status.channelsReady[index]);
			iceV_status.channelsReady[index] = true;
		}

		bool allReady = true;

		if(types & JingleRtp::Audio)
		{
			for(int n = 0; n < iceA_status.channelsReady.count(); ++n)
			{
				if(!iceA_status.channelsReady[n])
				{
					allReady = false;
					break;
				}
			}
		}

		if(types & JingleRtp::Video)
		{
			for(int n = 0; n < iceV_status.channelsReady.count(); ++n)
			{
				if(!iceV_status.channelsReady[n])
				{
					allReady = false;
					break;
				}
			}
		}

		if(allReady)
		{
			ice_connected = true;
			after_ice_connected();
		}
	}

	void task_finished()
	{
		if(!jt)
			return;

		JT_JingleRtp *task = jt;
		jt = 0;

		if(task->success())
			prov_accept();
		else
			prov_reject();
	}

	void after_session_accept()
	{
		tryActivated();
	}
};

JingleRtp::JingleRtp()
{
	d = new JingleRtpPrivate(this);
}

JingleRtp::~JingleRtp()
{
	delete d;
}

XMPP::Jid JingleRtp::jid() const
{
	return d->peer;
}

QList<JingleRtpPayloadType> JingleRtp::remoteAudioPayloadTypes() const
{
	return d->remoteAudioPayloadTypes;
}

QList<JingleRtpPayloadType> JingleRtp::remoteVideoPayloadTypes() const
{
	return d->remoteVideoPayloadTypes;
}

int JingleRtp::remoteMaximumBitrate() const
{
	return d->remoteMaximumBitrate;
}

void JingleRtp::setLocalAudioPayloadTypes(const QList<JingleRtpPayloadType> &types)
{
	d->localAudioPayloadTypes = types;
}

void JingleRtp::setLocalVideoPayloadTypes(const QList<JingleRtpPayloadType> &types)
{
	d->localVideoPayloadTypes = types;
}

void JingleRtp::setLocalMaximumBitrate(int kbps)
{
	d->localMaximumBitrate = kbps;
}

void JingleRtp::connectToJid(const XMPP::Jid &jid)
{
	d->peer = jid;
	d->startOutgoing();
}

void JingleRtp::accept(int types)
{
	d->accept(types);
}

void JingleRtp::reject()
{
	d->reject();
}

void JingleRtp::localMediaUpdate()
{
	d->localMediaUpdate();
}

JingleRtp::Error JingleRtp::errorCode() const
{
	return d->errorCode;
}

JingleRtpChannel *JingleRtp::rtpChannel()
{
	return d->rtpChannel;
}

//----------------------------------------------------------------------------
// JingleRtpChannel
//----------------------------------------------------------------------------
JingleRtpChannelPrivate::JingleRtpChannelPrivate(JingleRtpChannel *_q) :
	QObject(_q),
	q(_q),
	portReserver(0),
	iceA(0),
	iceV(0)
{
	rtpActivityTimer = new QTimer(this);
	connect(rtpActivityTimer, SIGNAL(timeout()), SLOT(rtpActivity_timeout()));
}

JingleRtpChannelPrivate::~JingleRtpChannelPrivate()
{
	if(portReserver)
	{
		QList<XMPP::Ice176*> list;

		portReserver->setParent(0);

		if(iceA)
		{
			iceA->disconnect(this);
			iceA->setParent(0);
			//iceA->deleteLater();
			list += iceA;
		}

		if(iceV)
		{
			iceV->disconnect(this);
			iceV->setParent(0);
			//iceV->deleteLater();
			list += iceV;
		}

		// pass ownership of portReserver, iceA, and iceV
		IceStopper *iceStopper = new IceStopper;
		iceStopper->start(portReserver, list);
	}

	rtpActivityTimer->setParent(0);
	rtpActivityTimer->disconnect(this);
	rtpActivityTimer->deleteLater();
}

void JingleRtpChannelPrivate::setIceObjects(XMPP::UdpPortReserver *_portReserver, XMPP::Ice176 *_iceA, XMPP::Ice176 *_iceV)
{
	if(QThread::currentThread() != thread())
	{
		// if called from another thread, safely change ownership
		QMutexLocker locker(&m);

		portReserver = _portReserver;
		iceA = _iceA;
		iceV = _iceV;

		if(portReserver)
			portReserver->moveToThread(thread());

		if(iceA)
		{
			iceA->moveToThread(thread());
			connect(iceA, SIGNAL(readyRead(int)), SLOT(ice_readyRead(int)));
			connect(iceA, SIGNAL(datagramsWritten(int, int)), SLOT(ice_datagramsWritten(int, int)));
		}

		if(iceV)
		{
			iceV->moveToThread(thread());
			connect(iceV, SIGNAL(readyRead(int)), SLOT(ice_readyRead(int)));
			connect(iceV, SIGNAL(datagramsWritten(int, int)), SLOT(ice_datagramsWritten(int, int)));
		}

		QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
	}
	else
	{
		portReserver = _portReserver;
		iceA = _iceA;
		iceV = _iceV;

		if(iceA)
		{
			connect(iceA, SIGNAL(readyRead(int)), SLOT(ice_readyRead(int)));
			connect(iceA, SIGNAL(datagramsWritten(int, int)), SLOT(ice_datagramsWritten(int, int)));
		}

		if(iceV)
		{
			connect(iceV, SIGNAL(readyRead(int)), SLOT(ice_readyRead(int)));
			connect(iceV, SIGNAL(datagramsWritten(int, int)), SLOT(ice_datagramsWritten(int, int)));
		}

		start();
	}
}

void JingleRtpChannelPrivate::restartRtpActivityTimer()
{
	// if we go 5 seconds without an RTP packet, then that's
	//   pretty bad
	rtpActivityTimer->start(5000);
}

void JingleRtpChannelPrivate::start()
{
	if(portReserver)
		portReserver->setParent(this);
	if(iceA)
		iceA->setParent(this);
	if(iceV)
		iceV->setParent(this);
	restartRtpActivityTimer();
}

void JingleRtpChannelPrivate::ice_readyRead(int componentIndex)
{
	XMPP::Ice176 *ice = (XMPP::Ice176 *)sender();

	if(ice == iceA && componentIndex == 0)
		restartRtpActivityTimer();

	if(ice == iceA)
	{
		while(iceA->hasPendingDatagrams(componentIndex))
		{
			JingleRtp::RtpPacket packet;
			packet.type = JingleRtp::Audio;
			packet.portOffset = componentIndex;
			packet.value = iceA->readDatagram(componentIndex);
			in += packet;
		}
	}
	else // iceV
	{
		while(iceV->hasPendingDatagrams(componentIndex))
		{
			JingleRtp::RtpPacket packet;
			packet.type = JingleRtp::Video;
			packet.portOffset = componentIndex;
			packet.value = iceV->readDatagram(componentIndex);
			in += packet;
		}
	}

	emit q->readyRead();
}

void JingleRtpChannelPrivate::ice_datagramsWritten(int componentIndex, int count)
{
	Q_UNUSED(componentIndex);

	emit q->packetsWritten(count);
}

void JingleRtpChannelPrivate::rtpActivity_timeout()
{
	printf("warning: 5 seconds passed without receiving audio RTP\n");
}

JingleRtpChannel::JingleRtpChannel()
{
	d = new JingleRtpChannelPrivate(this);
}

JingleRtpChannel::~JingleRtpChannel()
{
	delete d;
}

bool JingleRtpChannel::packetsAvailable() const
{
	return !d->in.isEmpty();
}

JingleRtp::RtpPacket JingleRtpChannel::read()
{
	return d->in.takeFirst();
}

void JingleRtpChannel::write(const JingleRtp::RtpPacket &packet)
{
	QMutexLocker locker(&d->m);

	if(packet.type == JingleRtp::Audio && d->iceA)
		d->iceA->writeDatagram(packet.portOffset, packet.value);
	else if(packet.type == JingleRtp::Video && d->iceV)
		d->iceV->writeDatagram(packet.portOffset, packet.value);
}

//----------------------------------------------------------------------------
// JingleRtpManager
//----------------------------------------------------------------------------
JingleRtpManagerPrivate::JingleRtpManagerPrivate(XMPP::Client *_client, JingleRtpManager *_q) :
	QObject(_q),
	q(_q),
	client(_client),
	stunBindPort(-1),
	stunRelayUdpPort(-1),
	stunRelayTcpPort(-1),
	basePort(-1)
{
	push_task = new JT_PushJingleRtp(client->rootTask());
	connect(push_task, SIGNAL(incomingRequest(const XMPP::Jid &, const QString &, const JingleRtpEnvelope &)), SLOT(push_task_incomingRequest(const XMPP::Jid &, const QString &, const JingleRtpEnvelope &)));
}

JingleRtpManagerPrivate::~JingleRtpManagerPrivate()
{
	delete push_task;
}

QString JingleRtpManagerPrivate::createSid(const XMPP::Jid &peer) const
{
	while(1)
	{
		QString out = randomCredential(16);

		bool found = false;
		for(int n = 0; n < sessions.count(); ++n)
		{
			if(sessions[n]->d->peer == peer && sessions[n]->d->sid == out)
			{
				found = true;
				break;
			}
		}

		if(!found)
			return out;
	}
}

void JingleRtpManagerPrivate::unlink(JingleRtp *sess)
{
	sessions.removeAll(sess);
}

void JingleRtpManagerPrivate::push_task_incomingRequest(const XMPP::Jid &from, const QString &iq_id, const JingleRtpEnvelope &envelope)
{
	printf("incoming request: [%s]\n", qPrintable(envelope.action));

	// don't allow empty sid
	if(envelope.sid.isEmpty())
	{
		push_task->respondError(from, iq_id, 400, QString());
		return;
	}

	if(envelope.action == "session-initiate")
	{
		int at = -1;
		for(int n = 0; n < sessions.count(); ++n)
		{
			if(sessions[n]->d->peer == from && sessions[n]->d->sid == envelope.sid)
			{
				at = n;
				break;
			}
		}

		// duplicate session
		if(at != -1)
		{
			push_task->respondError(from, iq_id, 400, QString());
			return;
		}

		JingleRtp *sess = new JingleRtp;
		sess->d->manager = this;
		sess->d->incoming = true;
		sess->d->peer = from;
		sess->d->sid = envelope.sid;
		sessions += sess;
		printf("new initiate, from=[%s] sid=[%s]\n", qPrintable(from.full()), qPrintable(envelope.sid));
		if(!sess->d->incomingRequest(iq_id, envelope))
		{
			delete sess;
			push_task->respondError(from, iq_id, 400, QString());
			return;
		}

		pending += sess;
		emit q->incomingReady();
	}
	else
	{
		int at = -1;
		for(int n = 0; n < sessions.count(); ++n)
		{
			if(sessions[n]->d->peer == from && sessions[n]->d->sid == envelope.sid)
			{
				at = n;
				break;
			}
		}

		// session not found
		if(at == -1)
		{
			push_task->respondError(from, iq_id, 400, QString());
			return;
		}

		sessions[at]->d->incomingRequest(iq_id, envelope);
	}
}

JingleRtpManager::JingleRtpManager(XMPP::Client *client) :
	QObject(0)
{
	d = new JingleRtpManagerPrivate(client, this);
}

JingleRtpManager::~JingleRtpManager()
{
	delete d;
}

JingleRtp *JingleRtpManager::createOutgoing()
{
	JingleRtp *sess = new JingleRtp;
	sess->d->manager = d;
	sess->d->incoming = false;
	d->sessions += sess;
	return sess;
}

JingleRtp *JingleRtpManager::takeIncoming()
{
	return d->pending.takeFirst();
}

void JingleRtpManager::setSelfAddress(const QHostAddress &addr)
{
	d->selfAddr = addr;
}

void JingleRtpManager::setExternalAddress(const QString &host)
{
	d->extHost = host;
}

void JingleRtpManager::setStunBindService(const QString &host, int port)
{
	d->stunBindHost = host;
	d->stunBindPort = port;
}

void JingleRtpManager::setStunRelayUdpService(const QString &host, int port, const QString &user, const QString &pass)
{
	d->stunRelayUdpHost = host;
	d->stunRelayUdpPort = port;
	d->stunRelayUdpUser = user;
	d->stunRelayUdpPass = pass;
}

void JingleRtpManager::setStunRelayTcpService(const QString &host, int port, const XMPP::AdvancedConnector::Proxy &proxy, const QString &user, const QString &pass)
{
	d->stunRelayTcpHost = host;
	d->stunRelayTcpPort = port;
	d->stunRelayTcpUser = user;
	d->stunRelayTcpPass = pass;

	XMPP::TurnClient::Proxy tproxy;

	if(proxy.type() == XMPP::AdvancedConnector::Proxy::HttpConnect)
	{
		tproxy.setHttpConnect(proxy.host(), proxy.port());
		tproxy.setUserPass(proxy.user(), proxy.pass());
	}
	else if(proxy.type() == XMPP::AdvancedConnector::Proxy::Socks)
	{
		tproxy.setSocks(proxy.host(), proxy.port());
		tproxy.setUserPass(proxy.user(), proxy.pass());
	}

	d->stunProxy = tproxy;
}

void JingleRtpManager::setBasePort(int port)
{
	d->basePort = port;
}

#include "jinglertp.moc"
