/*
 * protocol.h - XMPP-Core protocol state machine
 * Copyright (C) 2004  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifndef PROTOCOL_H
#define PROTOCOL_H

#include <tqpair.h>
#include "xmlprotocol.h"
#include "xmpp.h"

#define NS_ETHERX   "http://etherx.jabber.org/streams"
#define NS_CLIENT   "jabber:client"
#define NS_SERVER   "jabber:server"
#define NS_DIALBACK "jabber:server:dialback"
#define NS_STREAMS  "urn:ietf:params:xml:ns:xmpp-streams"
#define NS_TLS      "urn:ietf:params:xml:ns:xmpp-tls"
#define NS_SASL     "urn:ietf:params:xml:ns:xmpp-sasl"
#define NS_SESSION  "urn:ietf:params:xml:ns:xmpp-session"
#define NS_STANZAS  "urn:ietf:params:xml:ns:xmpp-stanzas"
#define NS_BIND     "urn:ietf:params:xml:ns:xmpp-bind"
#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im"
#define NS_XHTML "http://www.w3.org/1999/xhtml"
#define NS_CHATSTATES "http://jabber.org/protocol/chatstates"

namespace XMPP
{
	class Version
	{
	public:
		Version(int maj=0, int min=0);

		int major, minor;
	};

	class StreamFeatures
	{
	public:
		StreamFeatures();

		bool tls_supported, sasl_supported, bind_supported;
		bool tls_required;
		TQStringList sasl_mechs;
	};

	class BasicProtocol : public XmlProtocol
	{
	public:
		// xmpp 1.0 error conditions
		enum SASLCond {
			Aborted,
			IncorrectEncoding,
			InvalidAuthzid,
			InvalidMech,
			MechTooWeak,
			NotAuthorized,
			TemporaryAuthFailure
		};
		enum StreamCond {
			BadFormat,
			BadNamespacePrefix,
			Conflict,
			ConnectionTimeout,
			HostGone,
			HostUnknown,
			ImproperAddressing,
			InternalServerError,
			InvalidFrom,
			InvalidId,
			InvalidNamespace,
			InvalidXml,
			StreamNotAuthorized,
			PolicyViolation,
			RemoteConnectionFailed,
			ResourceConstraint,
			RestrictedXml,
			SeeOtherHost,
			SystemShutdown,
			UndefinedCondition,
			UnsupportedEncoding,
			UnsupportedStanzaType,
			UnsupportedVersion,
			XmlNotWellFormed
		};
		enum BindCond {
			BindBadRequest,
			BindNotAllowed,
			BindConflict
		};

		// extend the XmlProtocol enums
		enum Need {
			NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist
			NStartTLS,  // need to switch on TLS layer
			NSASLFirst, // need SASL first step
			NSASLNext,  // need SASL next step
			NSASLLayer, // need to switch on SASL layer
			NCustom = XmlProtocol::NCustom+10
		};
		enum Event {
			EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received
			ESASLSuccess, // breakpoint after successful sasl auth
			EStanzaReady, // a stanza was received
			EStanzaSent,  // a stanza was sent
			EReady,       // stream is ready for stanza use
			ECustom = XmlProtocol::ECustom+10
		};
		enum Error {
			ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange
			ErrStream,   // <stream:error>, see errCond, errText, and errAppSpec for details
			ErrStartTLS, // server refused starttls
			ErrAuth,     // authorization error.  errCond holds sasl condition (or numeric code for old-protocol)
			ErrBind,     // server refused resource bind
			ErrCustom = XmlProtocol::ErrCustom+10
		};

		BasicProtocol();
		~BasicProtocol();

		void reset();

		// for outgoing xml
		TQDomDocument doc;

		// sasl-related
		TQString saslMech() const;
		TQByteArray saslStep() const;
		void setSASLMechList(const TQStringList &list);
		void setSASLFirst(const TQString &mech, const TQByteArray &step);
		void setSASLNext(const TQByteArray &step);
		void setSASLAuthed();

		// send / recv
		void sendStanza(const TQDomElement &e);
		void sendDirect(const TQString &s);
		void sendWhitespace();
		TQDomElement recvStanza();

		// shutdown
		void shutdown();
		void shutdownWithError(int cond, const TQString &otherHost="");

		// <stream> information
		TQString to, from, id, lang;
		Version version;

		// error output
		int errCond;
		TQString errText;
		TQDomElement errAppSpec;
		TQString otherHost;

		TQByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer

		bool isReady() const;

		enum { TypeElement, TypeStanza, TypeDirect, TypePing };

	protected:
		static int stringToSASLCond(const TQString &s);
		static int stringToStreamCond(const TQString &s);
		static TQString saslCondToString(int);
		static TQString streamCondToString(int);

		void send(const TQDomElement &e, bool clip=false);
		void sendStreamError(int cond, const TQString &text="", const TQDomElement &appSpec=TQDomElement());
		void sendStreamError(const TQString &text); // old-style

		bool errorAndClose(int cond, const TQString &text="", const TQDomElement &appSpec=TQDomElement());
		bool error(int code);
		void delayErrorAndClose(int cond, const TQString &text="", const TQDomElement &appSpec=TQDomElement());
		void delayError(int code);

		// reimplemented
		TQDomElement docElement();
		void handleDocOpen(const Parser::Event &pe);
		bool handleError();
		bool handleCloseFinished();
		bool doStep(const TQDomElement &e);
		void itemWritten(int id, int size);

		virtual TQString defaultNamespace();
		virtual TQStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...]
		virtual void handleStreamOpen(const Parser::Event &pe);
		virtual bool doStep2(const TQDomElement &e)=0;

		void setReady(bool b);

		TQString sasl_mech;
		TQStringList sasl_mechlist;
		TQByteArray sasl_step;
		bool sasl_authed;

		TQDomElement stanzaToRecv;

	private:
		struct SASLCondEntry
		{
			const char *str;
			int cond;
		};
		static SASLCondEntry saslCondTable[];

		struct StreamCondEntry
		{
			const char *str;
			int cond;
		};
		static StreamCondEntry streamCondTable[];

		struct SendItem
		{
			TQDomElement stanzaToSend;
			TQString stringToSend;
			bool doWhitespace;
		};
		TQValueList<SendItem> sendList;

		bool doShutdown, delayedError, closeError, ready;
		int stanzasPending, stanzasWritten;

		void init();
		void extractStreamError(const TQDomElement &e);
	};

	class CoreProtocol : public BasicProtocol
	{
	public:
		enum {
			NPassword = NCustom,  // need password for old-mode
			EDBVerify = ECustom,  // breakpoint after db:verify request
			ErrPlain = ErrCustom  // server only supports plain, but allowPlain is false locally
		};

		CoreProtocol();
		~CoreProtocol();

		void reset();

		void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth);
		void startServerOut(const TQString &to);
		void startDialbackOut(const TQString &to, const TQString &from);
		void startDialbackVerifyOut(const TQString &to, const TQString &from, const TQString &id, const TQString &key);
		void startClientIn(const TQString &id);
		void startServerIn(const TQString &id);

		void setLang(const TQString &s);
		void setAllowTLS(bool b);
		void setAllowBind(bool b);
		void setAllowPlain(bool b); // old-mode

		void setPassword(const TQString &s);
		void setFrom(const TQString &s);
		void setDialbackKey(const TQString &s);

		// input
		TQString user, host;

		// status
		bool old;

		StreamFeatures features;

		//static TQString xmlToString(const TQDomElement &e, bool clip=false);

		class DBItem
		{
		public:
			enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated };
			int type;
			Jid to, from;
			TQString key, id;
			bool ok;
		};

	private:
		enum Step {
			Start,
			Done,
			SendFeatures,
			GetRequest,
			HandleTLS,
			GetSASLResponse,
			IncHandleSASLSuccess,
			GetFeatures,        // read features packet
			HandleFeatures,     // act on features, by initiating tls, sasl, or bind
			GetTLSProceed,      // read <proceed/> tls response
			GetSASLFirst,       // perform sasl first step using provided data
			GetSASLChallenge,   // read server sasl challenge
			GetSASLNext,        // perform sasl next step using provided data
			HandleSASLSuccess,  // handle what must be done after reporting sasl success
			GetBindResponse,    // read bind response
			HandleAuthGet,      // send old-protocol auth-get
			GetAuthGetResponse, // read auth-get response
			HandleAuthSet,      // send old-protocol auth-set
			GetAuthSetResponse  // read auth-set response
		};

		TQValueList<DBItem> dbrequests, dbpending, dbvalidated;

		bool server, dialback, dialback_verify;
		int step;

		bool digest;
		bool tls_started, sasl_started;

		Jid jid;
		bool oldOnly;
		bool allowPlain;
		bool doTLS, doAuth, doBinding;
		TQString password;

		TQString dialback_id, dialback_key;
		TQString self_from;

		void init();
		static int getOldErrorCode(const TQDomElement &e);
		bool loginComplete();

		bool isValidStanza(const TQDomElement &e) const;
		bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item);
		bool normalStep(const TQDomElement &e);
		bool dialbackStep(const TQDomElement &e);

		// reimplemented
		bool stepAdvancesParser() const;
		bool stepRequiresElement() const;
		void stringSend(const TQString &s);
		void stringRecv(const TQString &s);
		TQString defaultNamespace();
		TQStringList extraNamespaces();
		void handleStreamOpen(const Parser::Event &pe);
		bool doStep2(const TQDomElement &e);
		void elementSend(const TQDomElement &e);
		void elementRecv(const TQDomElement &e);
	};
}

#endif
