/*
 * dtcp.h - direct connection protocol via tcp
 * Copyright (C) 2001, 2002  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

 /*
   TODO:

   better error handling
   support for loopback
   expanded handshake protocol with starttls/SSL
   timeouts

 */
 
#ifndef JABBER_DTCP_H
#define JABBER_DTCP_H

#include<qobject.h>
#include<qserversocket.h>
#include<qdom.h>
#include<qstring.h>
#include<qptrlist.h>
#include"jid.h"
#include"client.h"
#include"jidstream.h"

class QSocket;

namespace Jabber
{
	class Client;
	class DTCPManager;

	// handles dtcp at the socket level
	class DTCPSocketHandler : public QObject
	{
		Q_OBJECT
	public:
		enum { Active, Passive };
		DTCPSocketHandler(DTCPManager *);
		~DTCPSocketHandler();

		// active
		void handle(const QString &host, int port, const Jid &peer, const QString &key, const QString &verify);
		// passive
		void handle(int s);
		void close();

		int mode() const;
		Jid peer() const;
		QString key() const;
		QString verify() const;
		bool isEstablished() const;
		QHostAddress peerAddress() const;

		void write(const QByteArray &);
		QByteArray read();
		bool canRead() const;

	signals:
		void result(bool, const QString &);

		void connectionClosed();
		void delayedCloseFinished();
		void readyRead();
		void bytesWritten(int);
		void error(int);

	private slots:
		void ndns_done();

		void sock_connected();
		void sock_connectionClosed();
		void sock_delayedCloseFinished();
		void sock_readyRead();
		void sock_bytesWritten(int);
		void sock_error(int);

	private:
		void doSuccess();
		void doError(const QString &);
		QString extractLine(QByteArray *, bool *);
		void processLine(const QString &);

		class Private;
		Private *d;
	};

	// this is a dtcp connection.  use it much like a qsocket
	class DTCPConnection : public JidStreamHandler
	{
		Q_OBJECT
	public:
		enum { ErrAsk, ErrEstablish, ErrSocket };
		enum { Idle, Asking, Establishing, Waiting, Active };
		DTCPConnection(DTCPManager *);
		~DTCPConnection();

		void connectToJid(const Jid &peer, bool passive, const QDomElement &comment);
		void accept();
		void close();

		int state() const;
		bool isPassive() const;
		Jid peer() const;
		QString key() const;
		QString verify() const;
		QDomElement comment() const;
		bool isConnected() const;

		void write(const QByteArray &);
		QByteArray read();
		bool canRead() const;

	private slots:
		void dsh_result(bool, const QString &);
		void dsh_connectionClosed();
		void dsh_delayedCloseFinished();
		void dsh_readyRead();
		void dsh_bytesWritten(int);
		void dsh_error(int);

		void dtcp_finished();

	private:
		class Private;
		Private *d;

		void reset();

		friend class DTCPManager;
		friend class DTCPSocketHandler;

		// waitForSocket / setSocketHandler - for receiving dtcp active (wait for incoming socket)
		void waitForSocket(const Jid &peer, const QString &key, const QString &verify, const QDomElement &comment, const QString &iq_id);
		void setSocketHandler(DTCPSocketHandler *);

		// waitForResult / conn - for receiving dtcp passive (wait for 'ok', then connect)
		void waitForResult(const Jid &peer, const QString &key, const QString &verify, const QStringList &hostList, const QDomElement &comment, const QString &iq_id);
		void conn();

		// needed by the manager to figure out what to do
		bool wantsIncoming() const;

		void onError();
	};

	typedef QPtrList<DTCPConnection> DTCPConnectionList;
	typedef QPtrListIterator<DTCPConnection> DTCPConnectionListIt;
	class DTCPManager : public QObject
	{
		Q_OBJECT
	public:
		DTCPManager(Client *);
		~DTCPManager();

		Client *client() const;
		bool isActive() const;
		bool setServer(int port);
		int port() const;
		void setHostList(const QStringList &);
		QStringList hostList() const;

		DTCPConnection *findConnection(const Jid &, const QString &) const;
		const DTCPConnectionList *connectionList() const;

	signals:
		void incoming(DTCPConnection *);

	private slots:
		void connectionReady(int);
		void pdtcp_incoming(const Jid &from, const QString &id, const QString &key, const QDomElement &);
		void pdtcp_incomingReverse(const Jid &from, const QString &id, const QString &key, const QStringList &hostList, const QDomElement &);
		void pdtcp_incomingReverseResult(const Jid &from, const QString &key);
		void pdtcp_error(const Jid &from, const QString &key, int, const QString &);
		void dsh_result(bool, const QString &);

	private:
		class Private;
		Private *d;

		QString genKey() const;

		friend class DTCPConnection;
		void link(DTCPConnection *);
		void unlink(DTCPConnection *);
		void doAccept(DTCPConnection *c, const QString &id);
		void doAcceptReverse(DTCPConnection *c, const QString &id);
		QString genUniqueKey(const Jid &) const;
	};

	class JT_DTCP : public Task
	{
		Q_OBJECT
	public:
		JT_DTCP(Task *);
		~JT_DTCP();

		void request(const Jid &, const QString &, const QDomElement &);
		void requestReverse(const Jid &, const QString &, const QStringList &hostList, const QDomElement &);

		void onGo();
		bool take(const QDomElement &);

		QStringList hostList() const;
		QString key() const;
		QString verify() const;
		Jid jid() const;

	private:
		QDomElement iq;
		int mode;
		Jid to;
		QStringList v_hostList;
		QString v_key, v_verify;
	};

	class JT_PushDTCP : public Task
	{
		Q_OBJECT
	public:
		JT_PushDTCP(Task *);
		~JT_PushDTCP();

		void respondSuccess(const Jid &to, const QString &id, const QStringList &hostList, const QString &verify);
		void respondReverseSuccess(const Jid &to, const QString &id, const QString &verify);
		void respondError(const Jid &to, const QString &id, int code, const QString &str);

		bool take(const QDomElement &);

	signals:
		void incoming(const Jid &from, const QString &id, const QString &key, const QDomElement &);
		void incomingReverse(const Jid &from, const QString &id, const QString &key, const QStringList &hostList, const QDomElement &);
		void incomingReverseResult(const Jid &from, const QString &key);
		void error(const Jid &from, const QString &key, int code, const QString &str);
	};

	class ServSock : public QServerSocket
	{
		Q_OBJECT
	public:
		ServSock(int port);

	signals:
		void connectionReady(int);

	protected:
		// reimplemented
		void newConnection(int);
	};
}

#endif
