/*****************************************************************************

$Id$

File:     ed.h
Date:     06Apr06

Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
Gmail: blackhedd

This program is free software; you can redistribute it and/or modify
it under the terms of either: 1) 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; or 2) Ruby's License.

See the file COPYING for complete licensing information.

*****************************************************************************/

#ifndef __EventableDescriptor__H_
#define __EventableDescriptor__H_


class EventMachine_t; // forward reference
#ifdef WITH_SSL
class SslBox_t; // forward reference
#endif

bool SetSocketNonblocking (SOCKET);


/*************************
class EventableDescriptor
*************************/

class EventableDescriptor: public Bindable_t
{
	public:
		EventableDescriptor (int, EventMachine_t*);
		virtual ~EventableDescriptor();

		int GetSocket() {return MySocket;}
		void SetSocketInvalid() { MySocket = INVALID_SOCKET; }
		void Close();

		virtual void Read() = 0;
		virtual void Write() = 0;
		virtual void Heartbeat() = 0;

		// These methods tell us whether the descriptor
		// should be selected or polled for read/write.
		virtual bool SelectForRead() = 0;
		virtual bool SelectForWrite() = 0;

		// are we scheduled for a close, or in an error state, or already closed?
		bool ShouldDelete();
		// Do we have any data to write? This is used by ShouldDelete.
		virtual int GetOutboundDataSize() {return 0;}

		void ScheduleClose (bool after_writing);
		bool IsCloseScheduled();

		void SetEventCallback (void (*cb)(const char*, int, const char*, int));

		virtual bool GetPeername (struct sockaddr*) {return false;}
		virtual bool GetSockname (struct sockaddr*) {return false;}
		virtual bool GetSubprocessPid (pid_t*) {return false;}

		virtual void StartTls() {}
		virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer) {}

		#ifdef WITH_SSL
		virtual X509 *GetPeerCert() {return NULL;}
		#endif

		virtual float GetCommInactivityTimeout() {return 0.0;}
		virtual int SetCommInactivityTimeout (float value) {return 0;}

		#ifdef HAVE_EPOLL
		struct epoll_event *GetEpollEvent() { return &EpollEvent; }
		#endif

		virtual void StartProxy(const char*);
		virtual void StopProxy();

	private:
		bool bCloseNow;
		bool bCloseAfterWriting;

	protected:
		int MySocket;
		enum {
			// 4 seconds is too short, most other libraries default to OS settings
			// which in 2.6 kernel defaults to a 60 second connect timeout. 
			// 
			// Curl-Multi: http://curl.haxx.se/mail/lib-2001-01/0019.html
			//
			// updating to 50 seconds, so we catch it before the OS does

			// can easily be made an instance variable
			PendingConnectTimeout = 50000000 // now in usec
		};

		void (*EventCallback)(const char*, int, const char*, int);
		void _GenericInboundDispatch(const char*, int);

		Int64 CreatedAt;
		bool bCallbackUnbind;
		int UnbindReasonCode;
		char *ProxyTarget;

		#ifdef HAVE_EPOLL
		struct epoll_event EpollEvent;
		#endif

		EventMachine_t *MyEventMachine;
};



/*************************
class LoopbreakDescriptor
*************************/

class LoopbreakDescriptor: public EventableDescriptor
{
	public:
		LoopbreakDescriptor (int, EventMachine_t*);
		virtual ~LoopbreakDescriptor() {}

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat() {}

		virtual bool SelectForRead() {return true;}
		virtual bool SelectForWrite() {return false;}
};


/**************************
class ConnectionDescriptor
**************************/

class ConnectionDescriptor: public EventableDescriptor
{
	public:
		ConnectionDescriptor (int, EventMachine_t*);
		virtual ~ConnectionDescriptor();

		static int SendDataToConnection (const char*, const char*, int);
		static void CloseConnection (const char*, bool);
		static int ReportErrorStatus (const char*);

		int SendOutboundData (const char*, int);

		void SetConnectPending (bool f);

		void SetNotifyReadable (bool);
		void SetNotifyWritable (bool);

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat();

		virtual bool SelectForRead();
		virtual bool SelectForWrite();

		// Do we have any data to write? This is used by ShouldDelete.
		virtual int GetOutboundDataSize() {return OutboundDataSize;}

		virtual void StartTls();
		virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer);

		#ifdef WITH_SSL
		virtual X509 *GetPeerCert();
		virtual bool VerifySslPeer(const char*);
		virtual void AcceptSslPeer();
		#endif

		void SetServerMode() {bIsServer = true;}

		virtual bool GetPeername (struct sockaddr*);
		virtual bool GetSockname (struct sockaddr*);

		virtual float GetCommInactivityTimeout();
		virtual int SetCommInactivityTimeout (float value);


	protected:
		struct OutboundPage {
			OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
			void Free() {if (Buffer) free ((char*)Buffer); }
			const char *Buffer;
			int Length;
			int Offset;
		};

	protected:
		bool bConnectPending;

		bool bNotifyReadable;
		bool bNotifyWritable;

		bool bReadAttemptedAfterClose;
		bool bWriteAttemptedAfterClose;

		deque<OutboundPage> OutboundPages;
		int OutboundDataSize;

		#ifdef WITH_SSL
		SslBox_t *SslBox;
		std::string CertChainFilename;
		std::string PrivateKeyFilename;
		bool bHandshakeSignaled;
		bool bSslVerifyPeer;
		bool bSslPeerAccepted;
		#endif
		bool bIsServer;

		#ifdef HAVE_KQUEUE
		bool bGotExtraKqueueEvent;
		#endif

		Int64 LastIo;
		int InactivityTimeout;

	private:
		void _WriteOutboundData();
		void _DispatchInboundData (const char *buffer, int size);
		void _DispatchCiphertext();
		int _SendRawOutboundData (const char*, int);
		int _ReportErrorStatus();
		void _CheckHandshakeStatus();

};


/************************
class DatagramDescriptor
************************/

class DatagramDescriptor: public EventableDescriptor
{
	public:
		DatagramDescriptor (int, EventMachine_t*);
		virtual ~DatagramDescriptor();

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat();

		virtual bool SelectForRead() {return true;}
		virtual bool SelectForWrite();

		int SendOutboundData (const char*, int);
		int SendOutboundDatagram (const char*, int, const char*, int);

		// Do we have any data to write? This is used by ShouldDelete.
		virtual int GetOutboundDataSize() {return OutboundDataSize;}

		virtual bool GetPeername (struct sockaddr*);
		virtual bool GetSockname (struct sockaddr*);

    virtual float GetCommInactivityTimeout();
    virtual int SetCommInactivityTimeout (float value);

		static int SendDatagram (const char*, const char*, int, const char*, int);


	protected:
		struct OutboundPage {
			OutboundPage (const char *b, int l, struct sockaddr_in f, int o=0): Buffer(b), Length(l), Offset(o), From(f) {}
			void Free() {if (Buffer) free ((char*)Buffer); }
			const char *Buffer;
			int Length;
			int Offset;
			struct sockaddr_in From;
		};

		deque<OutboundPage> OutboundPages;
		int OutboundDataSize;

		struct sockaddr_in ReturnAddress;

		Int64 LastIo;
		int InactivityTimeout;
};


/************************
class AcceptorDescriptor
************************/

class AcceptorDescriptor: public EventableDescriptor
{
	public:
		AcceptorDescriptor (int, EventMachine_t*);
		virtual ~AcceptorDescriptor();

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat();

		virtual bool SelectForRead() {return true;}
		virtual bool SelectForWrite() {return false;}

		virtual bool GetSockname (struct sockaddr*);

		static void StopAcceptor (const char *binding);
};

/********************
class PipeDescriptor
********************/

#ifdef OS_UNIX
class PipeDescriptor: public EventableDescriptor
{
	public:
		PipeDescriptor (int, pid_t, EventMachine_t*);
		virtual ~PipeDescriptor();

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat();

		virtual bool SelectForRead();
		virtual bool SelectForWrite();

		int SendOutboundData (const char*, int);
		virtual int GetOutboundDataSize() {return OutboundDataSize;}

		virtual bool GetSubprocessPid (pid_t*);

	protected:
		struct OutboundPage {
			OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
			void Free() {if (Buffer) free ((char*)Buffer); }
			const char *Buffer;
			int Length;
			int Offset;
		};

	protected:
		bool bReadAttemptedAfterClose;
		Int64 LastIo;
		int InactivityTimeout;

		deque<OutboundPage> OutboundPages;
		int OutboundDataSize;

		pid_t SubprocessPid;

	private:
		void _DispatchInboundData (const char *buffer, int size);
};
#endif // OS_UNIX


/************************
class KeyboardDescriptor
************************/

class KeyboardDescriptor: public EventableDescriptor
{
	public:
		KeyboardDescriptor (EventMachine_t*);
		virtual ~KeyboardDescriptor();

		virtual void Read();
		virtual void Write();
		virtual void Heartbeat();

		virtual bool SelectForRead() {return true;}
		virtual bool SelectForWrite() {return false;}

	protected:
		bool bReadAttemptedAfterClose;
		Int64 LastIo;
		int InactivityTimeout;

	private:
		void _DispatchInboundData (const char *buffer, int size);
};


/***********************
class InotifyDescriptor
************************/

class InotifyDescriptor: public EventableDescriptor
{
	public:
		InotifyDescriptor (EventMachine_t*);
		virtual ~InotifyDescriptor();

		void Read();
		void Write();

		virtual void Heartbeat() {}
		virtual bool SelectForRead() {return true;}
		virtual bool SelectForWrite() {return false;}
};

#endif // __EventableDescriptor__H_


