/*
 *  C Implementation: tunnel
 *
 * Description:
 *
 *
 *
 *Copyright (C) 2011  Belledonne Comunications, Grenoble, France
 */

#ifndef __TUNNEL_CLIENT_MANAGER_H__
#define __TUNNEL_CLIENT_MANAGER_H__
#include "bctoolbox/crypto.h"
#include "linphone/core.h"
#include "linphone/tunnel.h"
#include <list>
#include <string>
#include <tunnel/client.hh>
#include <tunnel/udp_mirror.hh>

#ifndef USE_BELLESIP
extern "C" {
#include <eXosip2/eXosip_transport_hook.h>
}
#endif

namespace belledonnecomm {
/**
 * @addtogroup tunnel_client
 * @{
 **/
struct DualSocket {
	TunnelSocket *sendSocket;
	TunnelSocket *recvSocket;
};

/**
 * The TunnelManager class extends the LinphoneCore functionnality in order to provide an easy to use API to
 * - provision tunnel servers ip addresses and ports
 * - start/stop the tunneling service
 * - be informed of connection and disconnection events to the tunnel server
 * - perform auto-detection whether tunneling is required, based on a test of sending/receiving a flow of UDP packets.
 *
 * It takes in charge automatically the SIP registration procedure when connecting or disconnecting to a tunnel server.
 * No other action on LinphoneCore is required to enable full operation in tunnel mode.
 **/
class TunnelManager {

public:
	/**
	 * Add a tunnel server. At least one should be provided to be able to connect.
	 * When several addresses are provided, the tunnel client may try each of them until it gets connected.
	 *
	 * @param ip tunnMethod definition for '-isInitialStateOn' not foundel server ip address
	 * @param port tunnel server tls port, recommended value is 443
	 */
	void addServer(const char *ip, int port);
	/**
	 *Add tunnel server with auto detection capabilities
	 *
	 * @param ip tunnel server ip address
	 * @param port tunnel server tls port, recommended value is 443
	 * @param udpMirrorPort remote port on the tunnel server side  used to test udp reachability
	 * @param delay udp packet round trip delay in ms considered as acceptable. recommended value is 1000 ms.
	 */
	void addServer(const char *ip, int port, unsigned int udpMirrorPort, unsigned int delay);
	/**
	 * Add a tunnel server couple. At least one should be provided to be able to connect.
	 * This is used when using the dual socket mode where one client will connect to one ip and the other client to the
	 * other ip.
	 *
	 * @param ip1 server ip address n°1
	 * @param port1 tunnel server tls port, recommended value is 443
	 * @param ip2 server ip address n°2
	 * @param port2 tunnel server tls port, recommended value is 443
	 * @param udpMirrorPort remote port on the tunnel server 1 side  used to test udp reachability
	 * @param delay udp packet round trip delay in ms considered as acceptable. recommended value is 1000 ms.
	 */
	void addServerPair(
	    const char *ip1, int port1, const char *ip2, int port2, unsigned int udpMirrorPort, unsigned int delay);
	/**
	 * Add a tunnel server couple. At least one should be provided to be able to connect.
	 * This is used when using the dual socket mode where one client will connect to one ip and the other client to the
	 * other ip.
	 *
	 * @param ip1 server ip address n°1
	 * @param port1 tunnel server tls port, recommended value is 443
	 * @param ip2 server ip address n°2
	 * @param port2 tunnel server tls port, recommended value is 443
	 */
	void addServerPair(const char *ip1, int port1, const char *ip2, int port2);
	/**
	 * Removes all tunnel server address previously entered with addServer()
	 **/
	void cleanServers();

	/**
	 * Enables the dual socket mode. In this mode, we have to configure pairs or ServerAddr
	 * 2 TunneClient will be used, one for each IP and each one will only either send or receive the data stream.
	 * @param enable true to enable the DualMode, false otherwise
	 */
	void enableDualMode(bool enable);
	/**
	 * Returns whether or not the DualMode is enabled
	 * @return true if it is enabled, false otherwise
	 */
	bool isDualModeEnabled();
	/**
	 * Forces reconnection to the tunnel server.
	 * This method is useful when the device switches from wifi to Edge/3G or vice versa. In most cases the tunnel
	 *client socket won't be notified promptly that its connection is now zombie, so it is recommended to call this
	 *method that will cause the lost connection to be closed and new connection to be issued.
	 **/
	void reconnect();
	/**
	 * @brief setMode
	 * @param mode
	 */
	void setMode(LinphoneTunnelMode mode);
	/**
	 * @brief Return the tunnel mode
	 * @return #LinphoneTunnelMode
	 */
	LinphoneTunnelMode getMode() const;
	/**
	 * iOS only feature: specify http proxy credentials.
	 * When the iOS device has an http proxy configured in the iOS settings, the tunnel client will connect to the
	 *server through this http proxy. Credentials might be needed depending on the proxy configuration.
	 * @param username The username.
	 * @param passwd The password.
	 **/
	void setHttpProxyAuthInfo(const char *username, const char *passwd);
	void setHttpProxy(const char *host, int port, const char *username, const char *passwd);
	/**
	 * Indicate to the tunnel manager whether SIP packets must pass
	 * through the tunnel. That featurte is automatically enabled at
	 * the creation of the TunnelManager instance.
	 * @param enable If set to TRUE, SIP packets will pass through the tunnel.
	 * If set to FALSE, SIP packets will pass by the configured proxies.
	 */
	void tunnelizeSipPackets(bool enable);
	/**
	 * @brief Check whether the tunnel manager is set to tunnelize SIP packets
	 * @return True, SIP packets pass through the tunnel
	 */
	bool tunnelizeSipPacketsEnabled() const;
	/**
	 * Indicate to the tunnel manager wether server certificate
	 * must be verified during TLS handshake. Default: disabled
	 * @param enable If set to TRUE, SIP packets will pass through the tunnel.
	 * If set to FALSE, SIP packets will pass by the configured proxies.
	 */
	void verifyServerCertificate(bool enable);
	/**
	 * Check wether the tunnel manager is set to verify server certificate during TLS handshake
	 * @return True, server certificate is verified(using the linphonecore root certificate)
	 */
	bool verifyServerCertificateEnabled() const;
	/**
	 * @brief Constructor
	 * @param lc The LinphoneCore instance of which the TunnelManager will be associated to.
	 */
	TunnelManager(LinphoneCore *lc);
	/**
	 * @brief Destructor
	 */
	~TunnelManager();
	/**
	 * @brief Create an RtpTransport
	 * @param port
	 * @return
	 */
	RtpTransport *createRtpTransport(int port);
	/**
	 * @brief Destroy the given RtpTransport
	 * @param t
	 * @param s
	 */
	void closeRtpTransport(RtpTransport *t, TunnelSocket *s);
	/**
	 * @brief Get associated Linphone Core
	 * @return pointer on the associated LinphoneCore
	 */
	LinphoneCore *getLinphoneCore() const;
	/**
	 * @brief Check wehter the tunnel is connected
	 * @return True whether the tunnel is connected
	 */
	bool isConnected() const;

	bool isActivated() const;

	void simulateUdpLoss(bool enabled);

	void setUsername(const char *username);
	const std::string &getUsername() const;
	void setDomain(const char *domain);
	const std::string &getDomain() const;
	// Used for tests: returns true if given RtpTransport is for tunnelling.
	bool isTunnelRtpTransport(const RtpTransport *tp) const;

private:
	enum EventType {
		UdpMirrorClientEvent,
		TunnelEvent,
	};
	struct Event {
		EventType mType;
		union EventData {
			bool mConnected;
			bool mHaveUdp;
		} mData;
	};
	typedef std::list<UdpMirrorClient> UdpMirrorClientList;
	static int
	customSendto(struct _RtpTransport *t, mblk_t *msg, int flags, const struct sockaddr *to, socklen_t tolen);
	static int
	customRecvfrom(struct _RtpTransport *t, mblk_t *msg, int flags, struct sockaddr *from, socklen_t *fromlen);
	static void tunnelCallback(bool connected, void *zis);
	static void tunnelCallback2(TunnelDirection direction, bool connected, void *zis);
	static bool_t sOnIterate(TunnelManager *zis);
	static void sUdpMirrorClientCallback(bool result, void *data);
	static void networkReachableCb(LinphoneCore *lc, bool_t reachable);
	static void globalStateChangedCb(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message);

private:
	enum State {
		Off, /*no tunneling */
		On   /*tunneling activated*/
	};
	void onIterate();
	void doRegistration();
	void doUnregistration();
	void startClient();
	bool startAutoDetection();
	void processTunnelEvent(const Event &ev);
	void processUdpMirrorEvent(const Event &ev);
	void postEvent(const Event &ev);
	void stopClient();
	void stopAutoDetection();
	void stopLongRunningTask();
	void applyMode();
	void setState(State state);
	void applyState();
	void tunnelizeLiblinphone();
	void untunnelizeLiblinphone();
	void unlinkLinphoneCore();
	bctbx_x509_certificate_t *getCertificate() const;
	void setCertificate(bctbx_x509_certificate_t *certificate);
	bctbx_signing_key_t *getKey() const;
	void setKey(bctbx_signing_key_t *key);
	int tlsUpdateClientCertificate();

private:
	LinphoneCore *mCore;
	LinphoneTunnelMode mMode;
	TunnelClientI *mTunnelClient;
	std::string mHttpUserName;
	std::string mHttpPasswd;
	std::string mHttpProxyHost;
	int mHttpProxyPort;
	LinphoneCoreCbs *mCoreCbs;
	std::list<ServerAddr> mServerAddrs;
	std::list<DualServerAddr> mDualServerAddrs;
	UdpMirrorClientList mUdpMirrorClients;
	UdpMirrorClientList::iterator mCurrentUdpMirrorClient;
	LinphoneRtpTransportFactories mTransportFactories;
	Mutex mMutex;
	std::queue<Event> mEvq;
	char mLocalAddr[64];
	unsigned long mLongRunningTaskId;
	State mTargetState;
	State mState;
	bool mVerifyServerCertificate;
	bool mStarted;
	bool mAutodetectionRunning;
	bool mTunnelizeSipPackets;
	bool mSimulateUdpLoss;
	bool mUseDualClient;
	std::string mUsername;
	std::string mDomain;
	bctbx_x509_certificate_t *mCertificate;
	bctbx_signing_key_t *mKey;
};

/**
 * @}
 **/

} // namespace belledonnecomm

#endif /*__TUNNEL_CLIENT_MANAGER_H__*/
