/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QHash>

#include "qnetworksession_maemo_p.h"
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>

#include <maemo_icd.h>
#include <iapconf.h>
#include <proxyconf.h>

#include <sys/types.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

QTM_BEGIN_NAMESPACE

static QHash<QString, QVariant> properties;

static QString get_network_interface();
static DBusConnection *dbus_connection;
static DBusHandlerResult signal_handler(DBusConnection *connection,
					DBusMessage *message,
					void *user_data);

#define ICD_DBUS_MATCH		"type='signal'," \
				"interface='" ICD_DBUS_INTERFACE "'," \
				"path='" ICD_DBUS_PATH "'"


static inline DBusConnection *get_dbus_conn(DBusError *error)
{
    DBusConnection *conn = dbus_bus_get(DBUS_BUS_SYSTEM, error);
#ifdef BEARER_MANAGEMENT_DEBUG
    qDebug() << "Listening to bus" << dbus_bus_get_unique_name(conn);
#endif

    return conn;
}


/* Helper class that monitors the Icd status messages and
 * can change the IAP status accordingly. This is a singleton.
 */
class IcdListener : public QObject
{
    Q_OBJECT

public:
    IcdListener() : first_call(true) { }
    friend DBusHandlerResult signal_handler(DBusConnection *connection,
					    DBusMessage *message,
					    void *user_data);
    void setup(QNetworkSessionPrivate *d);
    void cleanup();
    void cleanupSession(QNetworkSessionPrivate *ptr);

    enum IapConnectionStatus {
	/* The IAP was connected */
	CONNECTED = 0,
	/* The IAP was disconnected */
	DISCONNECTED,
	/* The IAP is disconnecting */
	DISCONNECTING,
	/* The IAP has a network address, but is not yet fully connected */
	NETWORK_UP
    };

private:
    void icdSignalReceived(QString&, QString&, QString&);
    bool first_call;
    QHash<QString, QNetworkSessionPrivate* > sessions;
};

Q_GLOBAL_STATIC(IcdListener, icdListener);
 

static DBusHandlerResult signal_handler(DBusConnection *,
					DBusMessage *message,
					void *user_data)
{
    if (dbus_message_is_signal(message,
				ICD_DBUS_INTERFACE,
				ICD_STATUS_CHANGED_SIG)) {

	IcdListener *icd = (IcdListener *)user_data;
	DBusError error;
	dbus_error_init(&error);

	char *iap_id = 0;
	char *network_type = 0;
	char *state = 0;

	if (dbus_message_get_args(message, &error,
				    DBUS_TYPE_STRING, &iap_id,
				    DBUS_TYPE_STRING, &network_type,
				    DBUS_TYPE_STRING, &state,
				    DBUS_TYPE_INVALID) == FALSE) {
	    qWarning() << QString("Failed to parse icd status signal: %1").arg(error.message);
        } else {
	    QString _iap_id(iap_id);
	    QString _network_type(network_type);
	    QString _state(state);

	    icd->icdSignalReceived(_iap_id, _network_type, _state);
	}

	dbus_error_free(&error);
        return DBUS_HANDLER_RESULT_HANDLED;
    }

    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}


void IcdListener::setup(QNetworkSessionPrivate *d)
{
    if (first_call) {
	// We use the old Icd dbus interface like in ConIC
	DBusError error;
	dbus_error_init(&error);

	dbus_connection = get_dbus_conn(&error);
	if (dbus_error_is_set(&error)) {
	    qWarning() << "Cannot get dbus connection.";
	    dbus_error_free(&error);
	    return;
	}

	static struct DBusObjectPathVTable icd_vtable;
	icd_vtable.message_function = signal_handler;

	dbus_bus_add_match(dbus_connection, ICD_DBUS_MATCH, &error);
	if (dbus_error_is_set(&error)) {
	    qWarning() << "Cannot add match" << ICD_DBUS_MATCH;
	    dbus_error_free(&error);
	    return;
	}

	if (dbus_connection_register_object_path(dbus_connection,
						    ICD_DBUS_PATH,
						    &icd_vtable,
						    (void*)this) == FALSE) {
	    qWarning() << "Cannot register dbus signal handler, interface"<< ICD_DBUS_INTERFACE << "path" << ICD_DBUS_PATH;
	    dbus_error_free(&error);
	    return;
	}

#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "Listening" << ICD_STATUS_CHANGED_SIG << "signal from" << ICD_DBUS_SERVICE;
#endif
	first_call = false;
	dbus_error_free(&error);
    }

    QString id = d->activeConfig.identifier();
    if (!sessions.contains(id)) {
	QNetworkSessionPrivate *ptr = d;
	sessions.insert(id, ptr);
    }
}


void IcdListener::icdSignalReceived(QString& iap_id,
#ifdef BEARER_MANAGEMENT_DEBUG
				    QString& network_type,
#else
				    QString&,
#endif
				    QString& state)
{
    if (iap_id == OSSO_IAP_SCAN) // icd sends scan status signals which we will ignore
	return;

#ifdef BEARER_MANAGEMENT_DEBUG
    qDebug() << "Status received:" << iap_id << "type" << network_type << "state" << state;
#endif

    if (!sessions.contains(iap_id)) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "No session for IAP" << iap_id;
#endif
	return;
    }

    QNetworkSessionPrivate *session = sessions.value(iap_id);
    QNetworkConfiguration ap_conf = session->manager.configurationFromIdentifier(iap_id);
    if (!ap_conf.isValid()) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "Unknown IAP" << iap_id;
#endif
	return;
    }

    IapConnectionStatus status;

    if (state == "IDLE") {
	status = DISCONNECTED;
    } else if (state == "CONNECTED") {
	status = CONNECTED;
    } else if (state == "NETWORKUP") {
	status = NETWORK_UP;
    } else {
	//qDebug() << "Unknown state" << state;
	return;
    }

    if (status == DISCONNECTED) {
	if (ap_conf.state() == QNetworkConfiguration::Active) {
	    /* The IAP was just disconnected by Icd */
	    session->updateState(QNetworkSession::Disconnected);
	} else {
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "Got a network disconnect when in state" << ap_conf.state();
#endif
	}
    } else if (status == CONNECTED) {
	/* The IAP was just connected by Icd */
	session->updateState(QNetworkSession::Connected);
	session->updateIdentifier(iap_id);

	if (session->publicConfig.identifier() == OSSO_IAP_ANY) {
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "IAP" << iap_id << "connected when connecting to" << OSSO_IAP_ANY;
#endif
	} else {
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "IAP" << iap_id << "connected";
#endif
	}
    }

    return;
}


void IcdListener::cleanup()
{
    if (!first_call) {
	dbus_bus_remove_match(dbus_connection, ICD_DBUS_MATCH, NULL);
	dbus_connection_unref(dbus_connection);
    }
}


void IcdListener::cleanupSession(QNetworkSessionPrivate *ptr)
{
    if (ptr->publicConfig.type() == QNetworkConfiguration::UserChoice)
        (void)sessions.take(ptr->activeConfig.identifier());
    else
        (void)sessions.take(ptr->publicConfig.identifier());
}


void QNetworkSessionPrivate::cleanupSession(void)
{
    icdListener()->cleanupSession(this);

    QObject::disconnect(q, SIGNAL(stateChanged(QNetworkSession::State)), this, SLOT(updateProxies(QNetworkSession::State)));
}


void QNetworkSessionPrivate::updateState(QNetworkSession::State newState)
{
    if( newState != state) {
        state = newState;

	if (state == QNetworkSession::Disconnected) {
            isOpen = false;
	    currentNetworkInterface.clear();
	    if (publicConfig.type() == QNetworkConfiguration::UserChoice)
		activeConfig.d->state = QNetworkConfiguration::Defined;
	    publicConfig.d->state = QNetworkConfiguration::Defined;

	} else if (state == QNetworkSession::Connected) {
            isOpen = true;
	    if (publicConfig.type() == QNetworkConfiguration::UserChoice) {
		activeConfig.d->state = QNetworkConfiguration::Active;
		activeConfig.d->type = QNetworkConfiguration::InternetAccessPoint;
	    }
	    publicConfig.d->state = QNetworkConfiguration::Active;
	}

	emit q->stateChanged(newState);
    }
}


void QNetworkSessionPrivate::updateIdentifier(QString &newId)
{
    if (publicConfig.type() == QNetworkConfiguration::UserChoice) {
	activeConfig.d->network_attrs |= ICD_NW_ATTR_IAPNAME;
	activeConfig.d->id = newId;
    } else {
	publicConfig.d->network_attrs |= ICD_NW_ATTR_IAPNAME;
	if (publicConfig.d->id != newId) {
	    qWarning() << "Your config id changed from" << publicConfig.d->id << "to" << newId;
	    publicConfig.d->id = newId;
	}
    }
}


quint64 QNetworkSessionPrivate::getStatistics(bool sent) const
{
    /* This could be also implemented by using the Maemo::Icd::statistics()
     * that gets the statistics data for a specific IAP. Change if
     * necessary.
     */
    Maemo::Icd icd;
    QList<Maemo::IcdStatisticsResult> stats_results;
    quint64 counter_rx = 0, counter_tx = 0;

    if (!icd.statistics(stats_results)) {
	return 0;
    }

    foreach (Maemo::IcdStatisticsResult res, stats_results) {
	if (res.params.network_attrs & ICD_NW_ATTR_IAPNAME) {
	    /* network_id is the IAP UUID */
	    if (QString(res.params.network_id.data()) == activeConfig.identifier()) {
		counter_tx = res.bytes_sent;
		counter_rx = res.bytes_received;
	    }
	} else {
	    /* We probably will never get to this branch */
	    QNetworkConfigurationPrivate *d = activeConfig.d.data();
	    if (res.params.network_id == d->network_id) {
		counter_tx = res.bytes_sent;
		counter_rx = res.bytes_received;
	    }
	}
    }

    if (sent)
	return counter_tx;
    else
	return counter_rx;
}


quint64 QNetworkSessionPrivate::bytesWritten() const
{
    return getStatistics(true);
}

quint64 QNetworkSessionPrivate::bytesReceived() const
{
    return getStatistics(false);
}

quint64 QNetworkSessionPrivate::activeTime() const
{
    if (startTime.isNull()) {
        return 0;
    }
    return startTime.secsTo(QDateTime::currentDateTime());
}


QNetworkConfiguration& QNetworkSessionPrivate::copyConfig(QNetworkConfiguration &fromConfig, QNetworkConfiguration &toConfig, bool deepCopy)
{
    if (deepCopy) {
        QNetworkConfigurationPrivate* cpPriv = new QNetworkConfigurationPrivate();
        QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> ptr(cpPriv);
        toConfig.d = ptr;
    }

    toConfig.d->name = fromConfig.d->name;
    toConfig.d->isValid = fromConfig.d->isValid;
    // Note that we do not copy id field here as the publicConfig does
    // not contain a valid IAP id.
    toConfig.d->state = fromConfig.d->state;
    toConfig.d->type = fromConfig.d->type;
    toConfig.d->roamingSupported = fromConfig.d->roamingSupported;
    toConfig.d->purpose = fromConfig.d->purpose;
    toConfig.d->network_id = fromConfig.d->network_id;
    toConfig.d->iap_type = fromConfig.d->iap_type;
    toConfig.d->network_attrs = fromConfig.d->network_attrs;
    toConfig.d->service_type = fromConfig.d->service_type;
    toConfig.d->service_id = fromConfig.d->service_id;
    toConfig.d->service_attrs = fromConfig.d->service_attrs;
    toConfig.d->manager = fromConfig.d->manager;

    return toConfig;
}


/* This is called by QNetworkSession constructor and it updates the current
 * state of the configuration.
 */
void QNetworkSessionPrivate::syncStateWithInterface()
{
    /* Start to listen Icd status messages. */
    icdListener()->setup(this);

    /* Initially we are not active although the configuration might be in
     * connected state.
     */
    isOpen = false;
    opened = false;

    QObject::connect(&manager, SIGNAL(updateCompleted()), this, SLOT(networkConfigurationsChanged()));

    if (publicConfig.d.data()) {
	QNetworkConfigurationManagerPrivate* mgr = (QNetworkConfigurationManagerPrivate*)publicConfig.d.data()->manager;
	if (mgr) {
	    QObject::connect(mgr, SIGNAL(configurationChanged(QNetworkConfiguration)),
			    this, SLOT(configurationChanged(QNetworkConfiguration)));
	} else {
	    qWarning()<<"Manager object not set when trying to connect configurationChanged signal. Your configuration object is not correctly setup, did you remember to call manager.updateConfigurations() before creating session object?";
	    state = QNetworkSession::Invalid;
	    lastError = QNetworkSession::UnknownSessionError;
	    return;
	}
    }

    QObject::connect(q, SIGNAL(stateChanged(QNetworkSession::State)), this, SLOT(updateProxies(QNetworkSession::State)));

    state = QNetworkSession::Invalid;
    lastError = QNetworkSession::UnknownSessionError;

    switch (publicConfig.type()) {
    case QNetworkConfiguration::InternetAccessPoint:
	activeConfig = publicConfig;
        break;
    case QNetworkConfiguration::ServiceNetwork:
	serviceConfig = publicConfig;
	break;
    case QNetworkConfiguration::UserChoice:
	// active config will contain correct data after open() has succeeded
	copyConfig(publicConfig, activeConfig);

	/* We create new configuration that holds the actual configuration
	 * returned by icd. This way publicConfig still contains the
	 * original user specified configuration.
	 *
	 * Note that the new activeConfig configuration is not inserted
	 * to configurationManager as manager class will get the newly
	 * connected configuration from gconf when the IAP is saved.
	 * This configuration manager update is done by IapMonitor class.
	 * If the ANY connection fails in open(), then the configuration
	 * data is not saved to gconf and will not be added to
	 * configuration manager IAP list.
	 */
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug()<<"New configuration created for" << publicConfig.identifier();
#endif
	break;
    default:
	/* Invalid configuration, no point continuing */
	return;
    }

    if (!activeConfig.isValid())
	return;

    /* Get the initial state from icd */
    Maemo::Icd icd;
    QList<Maemo::IcdStateResult> state_results;

    /* Update the active config from first connection, this is ok as icd
     * supports only one connection anyway.
     */
    if (icd.state(state_results) && !state_results.isEmpty()) {

	/* If we did not get full state back, then we are not
	 * connected and can skip the next part.
	 */
	if (!(state_results.first().params.network_attrs == 0 &&
		state_results.first().params.network_id.isEmpty())) {

	    /* If we try to connect to specific IAP and we get results back
	     * that tell the icd is actually connected to another IAP,
	     * then do not update current state etc.
	     */
	    if (publicConfig.type() == QNetworkConfiguration::UserChoice ||
		    publicConfig.d->id == state_results.first().params.network_id) {

		switch (state_results.first().state) {
		case ICD_STATE_DISCONNECTED:
		    state = QNetworkSession::Disconnected;
		    if (activeConfig.d.data())
			activeConfig.d->isValid = true;
		    break;
		case ICD_STATE_CONNECTING:
		    state = QNetworkSession::Connecting;
		    if (activeConfig.d.data())
			activeConfig.d->isValid = true;
		    break;
		case ICD_STATE_CONNECTED:
		    {
			if (!state_results.first().error.isEmpty())
			    break;
                        
                        const QString id = state_results.first().params.network_id;

			QNetworkConfigurationManagerPrivate *mgr = (QNetworkConfigurationManagerPrivate*)activeConfig.d.data()->manager;
			if (mgr->accessPointConfigurations.contains(id)) {
                            //we don't want the copied data if the config is already known by the manager
                            //just reuse it so that existing references to the old data get the same update
			    QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> priv = mgr->accessPointConfigurations.value(activeConfig.d->id);
                            activeConfig.d = priv;
                        }


			state = QNetworkSession::Connected;
			activeConfig.d->network_id = state_results.first().params.network_id;
			activeConfig.d->id = activeConfig.d->network_id;
			activeConfig.d->network_attrs = state_results.first().params.network_attrs;
			activeConfig.d->iap_type = state_results.first().params.network_type;
			activeConfig.d->service_type = state_results.first().params.service_type;
			activeConfig.d->service_id = state_results.first().params.service_id;
			activeConfig.d->service_attrs = state_results.first().params.service_attrs;
			activeConfig.d->type = QNetworkConfiguration::InternetAccessPoint;
			activeConfig.d->state = QNetworkConfiguration::Active;
			activeConfig.d->isValid = true;
			currentNetworkInterface = get_network_interface();

			Maemo::IAPConf iap_name(activeConfig.d->id);
			QString name_value = iap_name.value("name").toString();
			if (!name_value.isEmpty())
			    activeConfig.d->name = name_value;
			else
			    activeConfig.d->name = activeConfig.d->id;


			// Add the new active configuration to manager or update the old config
			mgr = (QNetworkConfigurationManagerPrivate*)activeConfig.d.data()->manager;
			if (!(mgr->accessPointConfigurations.contains(activeConfig.d->id))) {
			    QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> ptr = activeConfig.d;
			    mgr->accessPointConfigurations.insert(activeConfig.d->id, ptr);

			    QNetworkConfiguration item;
			    item.d = ptr;
			    emit mgr->configurationAdded(item);

#ifdef BEARER_MANAGEMENT_DEBUG
			    //qDebug()<<"New configuration"<<activeConfig.d->id<<"added to manager in sync";
#endif

			} else {
			    mgr->configurationChanged((QNetworkConfigurationPrivate*)(activeConfig.d.data()));
#ifdef BEARER_MANAGEMENT_DEBUG
			    //qDebug()<<"Existing configuration"<<activeConfig.d->id<<"updated in manager in sync";
#endif
			}

		    }
		    break;

		case ICD_STATE_DISCONNECTING:
		    state = QNetworkSession::Closing;
		    if (activeConfig.d.data())
			activeConfig.d->isValid = true;
		    break;
		default:
		    break;
		}
	    }
	} else {
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "status_req tells icd is not connected";
#endif
	}
    } else {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "status_req did not return any results from icd";
#endif
    }

    networkConfigurationsChanged();
}


void QNetworkSessionPrivate::networkConfigurationsChanged()
{
    if (serviceConfig.isValid())
        updateStateFromServiceNetwork();
    else
        updateStateFromActiveConfig();
}


void QNetworkSessionPrivate::updateStateFromServiceNetwork()
{
    QNetworkSession::State oldState = state;

    foreach (const QNetworkConfiguration &config, serviceConfig.children()) {
        if ((config.state() & QNetworkConfiguration::Active) != QNetworkConfiguration::Active)
            continue;

        if (activeConfig != config) {
            activeConfig = config;
            emit q->newConfigurationActivated();
        }

        state = QNetworkSession::Connected;
        if (state != oldState)
            emit q->stateChanged(state);

        return;
    }

    if (serviceConfig.children().isEmpty())
        state = QNetworkSession::NotAvailable;
    else
        state = QNetworkSession::Disconnected;

    if (state != oldState)
        emit q->stateChanged(state);
}


void QNetworkSessionPrivate::clearConfiguration(QNetworkConfiguration &config)
{
    config.d->network_id.clear();
    config.d->iap_type.clear();
    config.d->network_attrs = 0;
    config.d->service_type.clear();
    config.d->service_id.clear();
    config.d->service_attrs = 0;
}


void QNetworkSessionPrivate::updateStateFromActiveConfig()
{
    QNetworkSession::State oldState = state;

    bool newActive = false;

    if (!activeConfig.d.data())
	return;

    if (!activeConfig.isValid()) {
        state = QNetworkSession::Invalid;
	clearConfiguration(activeConfig);
    } else if ((activeConfig.state() & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) {
        state = QNetworkSession::Connected;
        newActive = opened;
    } else if ((activeConfig.state() & QNetworkConfiguration::Discovered) == QNetworkConfiguration::Discovered) {
        state = QNetworkSession::Disconnected;
    } else if ((activeConfig.state() & QNetworkConfiguration::Defined) == QNetworkConfiguration::Defined) {
        state = QNetworkSession::NotAvailable;
    } else if ((activeConfig.state() & QNetworkConfiguration::Undefined) == QNetworkConfiguration::Undefined) {
        state = QNetworkSession::NotAvailable;
	clearConfiguration(activeConfig);
    }

    bool oldActive = isOpen;
    isOpen = newActive;

    if (!oldActive && isOpen)
        emit quitPendingWaitsForOpened();

    if (oldActive && !isOpen)
        emit q->closed();

    if (oldState != state) {
        emit q->stateChanged(state);

	if (state == QNetworkSession::Disconnected) {
#ifdef BEARER_MANAGEMENT_DEBUG
	    //qDebug()<<"session aborted error emitted for"<<activeConfig.identifier();
#endif
	    lastError = QNetworkSession::SessionAbortedError;
	    emit q->error(lastError);
	}
    }

#ifdef BEARER_MANAGEMENT_DEBUG
    //qDebug()<<"oldState ="<<oldState<<" state ="<<state<<" oldActive ="<<oldActive<<" newActive ="<<newActive<<" opened ="<<opened;
#endif
}


void QNetworkSessionPrivate::configurationChanged(const QNetworkConfiguration &config)
{
    if (serviceConfig.isValid() && (config == serviceConfig || config == activeConfig))
        updateStateFromServiceNetwork();
    else if (config == activeConfig)
        updateStateFromActiveConfig();
}


static QString get_network_interface()
{
    Maemo::Icd icd;
    QList<Maemo::IcdAddressInfoResult> addr_results;
    uint ret;
    QString iface;

    ret = icd.addrinfo(addr_results);
    if (ret == 0) {
	/* No results */
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "Cannot get addrinfo from icd, are you connected or is icd running?";
#endif
	return iface;
    }

    const char *address = addr_results.first().ip_info.first().address.toAscii().constData();
    struct in_addr addr;
    if (inet_aton(address, &addr) == 0) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "address" << address << "invalid";
#endif
	return iface;
    }

    struct ifaddrs *ifaddr, *ifa;
    int family;

    if (getifaddrs(&ifaddr) == -1) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "getifaddrs() failed";
#endif
	return iface;
    }

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
	family = ifa->ifa_addr->sa_family;
	if (family != AF_INET) {
	    continue; /* Currently only IPv4 is supported by icd dbus interface */
	}
	if (((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr == addr.s_addr) {
	    iface = QString(ifa->ifa_name);
	    break;
	}
    }

    freeifaddrs(ifaddr);
    return iface;
}


void QNetworkSessionPrivate::open()
{
    if (serviceConfig.isValid()) {
        lastError = QNetworkSession::OperationNotSupportedError;
        emit q->error(lastError);
    } else if (!isOpen) {

	if (publicConfig.type() == QNetworkConfiguration::UserChoice) {
	    /* Caller is trying to connect to default IAP.
	     * At this time we will not know the IAP details so we just
	     * connect and update the active config when the IAP is
	     * connected.
	     */
	    opened = true;
            state = QNetworkSession::Connecting;
            emit q->stateChanged(state);
	    QTimer::singleShot(0, this, SLOT(do_open()));
	    return;
	}

	/* User is connecting to one specific IAP. If that IAP is not
	 * in discovered state we cannot continue.
	 */
        if ((activeConfig.state() & QNetworkConfiguration::Discovered) !=
            QNetworkConfiguration::Discovered) {
            lastError =QNetworkSession::InvalidConfigurationError;
            emit q->error(lastError);
            return;
        }
        opened = true;

        if ((activeConfig.state() & QNetworkConfiguration::Active) != QNetworkConfiguration::Active) {
            state = QNetworkSession::Connecting;
            emit q->stateChanged(state);

	    QTimer::singleShot(0, this, SLOT(do_open()));
	    return;
        }

        isOpen = (activeConfig.state() & QNetworkConfiguration::Active) == QNetworkConfiguration::Active;
        if (isOpen)
            emit quitPendingWaitsForOpened();
    } else {
	/* We seem to be active so inform caller */
	emit quitPendingWaitsForOpened();
    }
}


void QNetworkSessionPrivate::do_open()
{
    icd_connection_flags flags = connectFlags;
    bool st;
    QString result;
    QString iap = publicConfig.identifier();

    if (state == QNetworkSession::Connected) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "Already connected to" << activeConfig.identifier();
#endif
        emit q->stateChanged(QNetworkSession::Connected);
	emit quitPendingWaitsForOpened();
	return;
    }

    Maemo::IcdConnectResult connect_result;
    Maemo::Icd icd(ICD_LONG_CONNECT_TIMEOUT);
    QNetworkConfiguration config;
    if (publicConfig.type() == QNetworkConfiguration::UserChoice)
	config = activeConfig;
    else
	config = publicConfig;

    if (iap == OSSO_IAP_ANY) {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "connecting to default IAP" << iap;
#endif
	st = icd.connect(flags, connect_result);

    } else {

	QList<Maemo::ConnectParams> params;
	Maemo::ConnectParams param;
	param.connect.service_type = config.d->service_type;
	param.connect.service_attrs = config.d->service_attrs;
	param.connect.service_id = config.d->service_id;
	param.connect.network_type = config.d->iap_type;
	param.connect.network_attrs = config.d->network_attrs;
	if (config.d->network_attrs & ICD_NW_ATTR_IAPNAME)
	    param.connect.network_id = QByteArray(iap.toLatin1());
	else
	    param.connect.network_id = config.d->network_id;
	params.append(param);

#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug("connecting to %s/%s/0x%x/%s/0x%x/%s",
	    param.connect.network_id.data(),
	    param.connect.network_type.toAscii().constData(),
	    param.connect.network_attrs,
	    param.connect.service_type.toAscii().constData(),
	    param.connect.service_attrs,
	    param.connect.service_id.toAscii().constData());
#endif
	st = icd.connect(flags, params, connect_result);
    }

    if (st) {
	result = connect_result.connect.network_id.data();
	QString connected_iap = result;

	if (connected_iap.isEmpty()) {
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "connect to"<< iap << "failed, result is empty";
#endif
	    updateState(QNetworkSession::Disconnected);
	    emit q->error(QNetworkSession::InvalidConfigurationError);
	    if (publicConfig.type() == QNetworkConfiguration::UserChoice)
		cleanupAnyConfiguration();
	    return;
	}

	/* If the user tried to connect to some specific connection (foo)
	 * and we were already connected to some other connection (bar),
	 * then we cannot activate this session although icd has a valid
	 * connection to somewhere.
	 */
	if ((publicConfig.type() != QNetworkConfiguration::UserChoice) &&
	    (connected_iap != config.identifier())) {
	    updateState(QNetworkSession::Disconnected);
	    emit q->error(QNetworkSession::InvalidConfigurationError);
	    return;
	}


	/* Did we connect to non saved IAP? */
	if (!(config.d->network_attrs & ICD_NW_ATTR_IAPNAME)) {
	    /* Because the connection succeeded, the IAP is now known.
	     */
	    config.d->network_attrs |= ICD_NW_ATTR_IAPNAME;
	    config.d->id = connected_iap;
	}

	/* User might have changed the IAP name when a new IAP was saved */
	Maemo::IAPConf iap_name(config.d->id);
	QString name = iap_name.value("name").toString();
	if (!name.isEmpty())
	    config.d->name = name;

        config.d->iap_type = connect_result.connect.network_type;

	config.d->isValid = true;
	config.d->state = QNetworkConfiguration::Active;
	config.d->type = QNetworkConfiguration::InternetAccessPoint;

	startTime = QDateTime::currentDateTime();
	updateState(QNetworkSession::Connected);

	currentNetworkInterface = get_network_interface();

#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "connected to" << result << config.d->name << "at" << currentNetworkInterface;
#endif

	/* We first check if the configuration already exists in the manager
	 * and if it is not found there, we then insert it. Note that this
	 * is only done for user choice config only because it can be missing
	 * from config manager list.
	 */

	if (publicConfig.d->type == QNetworkConfiguration::UserChoice) {

#ifdef BEARER_MANAGEMENT_DEBUG
#if 0
	    QList<QNetworkConfiguration> configs;
	    QNetworkConfigurationManagerPrivate *conPriv = (QNetworkConfigurationManagerPrivate*)config.d.data()->manager;
	    QList<QString> cpsIdents = conPriv->accessPointConfigurations.keys();
	    foreach( QString ii, cpsIdents) {
		QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> p = 
		    conPriv->accessPointConfigurations.value(ii);
		QNetworkConfiguration pt;
		pt.d = conPriv->accessPointConfigurations.value(ii);
		configs << pt;
	    }

	    int all = configs.count();
	    qDebug() << "All configurations:" << all;
	    foreach(QNetworkConfiguration p, configs) {
		qDebug() << p.name() <<":  isvalid->" <<p.isValid() << " type->"<< p.type() << 
		    " roaming->" << p.isRoamingAvailable() << "identifier->" << p.identifier() <<
		    " purpose->" << p.purpose() << " state->" << p.state();
	    }
#endif
#endif

	    QNetworkConfigurationManagerPrivate *mgr = (QNetworkConfigurationManagerPrivate*)config.d.data()->manager;
            if (!mgr->accessPointConfigurations.contains(result)) {
		QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> ptr = config.d;
		mgr->accessPointConfigurations.insert(result, ptr);

		QNetworkConfiguration item;
		item.d = ptr;
		emit mgr->configurationAdded(item);

#ifdef BEARER_MANAGEMENT_DEBUG
		//qDebug()<<"New configuration"<<result<<"added to manager in open";
#endif

	    } else {
		QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> priv = mgr->accessPointConfigurations.value(result);
		QNetworkConfiguration reference;
		reference.d = priv;
                copyConfig(config, reference, false);
                config = reference;
                activeConfig = reference;
		mgr->configurationChanged((QNetworkConfigurationPrivate*)(config.d.data()));

#ifdef BEARER_MANAGEMENT_DEBUG
		//qDebug()<<"Existing configuration"<<result<<"updated in manager in open";
#endif
	    }
	}

	emit quitPendingWaitsForOpened();

    } else {
#ifdef BEARER_MANAGEMENT_DEBUG
	qDebug() << "connect to"<< iap << "failed, status:" << connect_result.status;
#endif
	updateState(QNetworkSession::Disconnected);
	if (publicConfig.type() == QNetworkConfiguration::UserChoice)
	    cleanupAnyConfiguration();
	emit q->error(QNetworkSession::UnknownSessionError);
    }
}


void QNetworkSessionPrivate::cleanupAnyConfiguration()
{
#ifdef BEARER_MANAGEMENT_DEBUG
    qDebug()<<"Removing configuration created for" << activeConfig.d->id;
#endif
    activeConfig = publicConfig;
}


void QNetworkSessionPrivate::close()
{
    if (serviceConfig.isValid()) {
        lastError = QNetworkSession::OperationNotSupportedError;
        emit q->error(lastError);
    } else if (isOpen) {
        opened = false;
        isOpen = false;
        emit q->closed();
    }
}


void QNetworkSessionPrivate::stop()
{
    if (serviceConfig.isValid()) {
        lastError = QNetworkSession::OperationNotSupportedError;
        emit q->error(lastError);
    } else {
        if ((activeConfig.state() & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) {
            state = QNetworkSession::Closing;
            emit q->stateChanged(state);

	    Maemo::Icd icd;
#ifdef BEARER_MANAGEMENT_DEBUG
	    qDebug() << "stopping session" << publicConfig.identifier();
#endif
	    icd.disconnect(ICD_CONNECTION_FLAG_APPLICATION_EVENT);
	    startTime = QDateTime();

	    /* Note that the state will go disconnected in
	     * updateStateFromActiveConfig() which gets called after
	     * configurationChanged is emitted (below).
	     */

	    activeConfig.d->state = QNetworkConfiguration::Discovered;
	    QNetworkConfigurationManagerPrivate *mgr = (QNetworkConfigurationManagerPrivate*)activeConfig.d.data()->manager;
	    mgr->configurationChanged((QNetworkConfigurationPrivate*)activeConfig.d.data());

	    opened = false;
	    isOpen = false;

        } else {
	    opened = false;
	    isOpen = false;
	    emit q->closed();
	}
    }
}


void QNetworkSessionPrivate::migrate()
{
    qWarning("This platform does not support roaming (%s).", __FUNCTION__);
}


void QNetworkSessionPrivate::accept()
{
    qWarning("This platform does not support roaming (%s).", __FUNCTION__);
}


void QNetworkSessionPrivate::ignore()
{
    qWarning("This platform does not support roaming (%s).", __FUNCTION__);
}


void QNetworkSessionPrivate::reject()
{
    qWarning("This platform does not support roaming (%s).", __FUNCTION__);
}


QNetworkInterface QNetworkSessionPrivate::currentInterface() const
{
    if (!publicConfig.isValid() || state != QNetworkSession::Connected)
        return QNetworkInterface();

    if (currentNetworkInterface.isEmpty())
        return QNetworkInterface();

    return QNetworkInterface::interfaceFromName(currentNetworkInterface);
}


void QNetworkSessionPrivate::setSessionProperty(const QString& key, const QVariant& value)
{
    if (value.isValid()) {
	properties.insert(key, value);

	if (key == "ConnectInBackground") {
	    bool v = value.toBool();
	    if (v)
		connectFlags = ICD_CONNECTION_FLAG_APPLICATION_EVENT;
	    else
		connectFlags = ICD_CONNECTION_FLAG_USER_EVENT;
	}
    } else {
	properties.remove(key);

	/* Set default value when property is removed */
	if (key == "ConnectInBackground")
	    connectFlags = ICD_CONNECTION_FLAG_USER_EVENT;
    }
}


QVariant QNetworkSessionPrivate::sessionProperty(const QString& key) const
{
    return properties.value(key);
}


QString QNetworkSessionPrivate::errorString() const
{
    QString errorStr;
    switch(q->error()) {
    case QNetworkSession::RoamingError:
        errorStr = QObject::tr("Roaming error");
        break;
    case QNetworkSession::SessionAbortedError:
        errorStr = QObject::tr("Session aborted by user or system");
        break;
    default:
    case QNetworkSession::UnknownSessionError:
        errorStr = QObject::tr("Unidentified Error");
        break;
    }
    return errorStr;
}


QNetworkSession::SessionError QNetworkSessionPrivate::error() const
{
    return QNetworkSession::UnknownSessionError;
}


void QNetworkSessionPrivate::updateProxies(QNetworkSession::State newState)
{
    if ((newState == QNetworkSession::Connected) &&
	(newState != currentState))
	updateProxyInformation();
    else if ((newState == QNetworkSession::Disconnected) &&
	    (currentState == QNetworkSession::Closing))
	clearProxyInformation();

    currentState = newState;
}


void QNetworkSessionPrivate::updateProxyInformation()
{
    Maemo::ProxyConf::update();
}


void QNetworkSessionPrivate::clearProxyInformation()
{
    Maemo::ProxyConf::clear();
}


#include "qnetworksession_maemo.moc"
#include "moc_qnetworksession_maemo_p.cpp"

QTM_END_NAMESPACE
