/* This file is part of KNemo
   Copyright (C) 2004, 2006 Percy Leonhardt <percy@eris23.de>

   KNemo is free software; you can redistribute it and/or modify
   it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   KNemo 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 Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <net/ethernet.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_mib.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <qmap.h>
#include <qregexp.h>
#include <qstringlist.h>

#include <kdebug.h>
#include <kprocess.h>
#include <kio/global.h>

#include "interfaceupdater.h"

#include "config.h"

InterfaceUpdater::InterfaceUpdater( QDict<Interface>& interfaceDict )
    : QObject(),
      mRouteProcess(0L),
      mIfconfigProcess(0L),
      mIwconfigProcess(0L),
      mInterfaceDict( interfaceDict )
{
}

InterfaceUpdater::~InterfaceUpdater()
{
#ifndef Q_OS_FREEBSD
    if ( mRouteProcess )
    {
        mRouteProcess->kill();
        delete mRouteProcess;
    }
    if ( mIfconfigProcess )
    {
        mIfconfigProcess->kill();
        delete mIfconfigProcess;
    }
    if ( mIwconfigProcess )
    {
        mIwconfigProcess->kill();
        delete mIwconfigProcess;
    }
#endif
}

void InterfaceUpdater::checkConfig()
{
#ifndef Q_OS_FREEBSD    
    if ( !mIfconfigProcess )
    {
        mIfconfigStdout = QString::null;
        mIfconfigProcess = new KProcess();
        mIfconfigProcess->setEnvironment( "LANG", "C" );
        mIfconfigProcess->setEnvironment( "LC_ALL", "C" );
        *mIfconfigProcess << PATH_IFCONFIG << "-a";
        connect( mIfconfigProcess,  SIGNAL( receivedStdout( KProcess*, char*, int ) ),
                 this, SLOT( ifconfigProcessStdout( KProcess*, char*, int ) ) );
        connect( mIfconfigProcess,  SIGNAL( processExited( KProcess* ) ),
                 this, SLOT( ifconfigProcessExited( KProcess* ) ) );

        if ( !mIfconfigProcess->start( KProcess::NotifyOnExit, KProcess::Stdout ) )
        {
            delete mIfconfigProcess;
            mIfconfigProcess = 0L;
        }
    }

#ifdef PATH_IWCONFIG
    if ( !mIwconfigProcess )
    {
        mIwconfigStdout = QString::null;
        mIwconfigProcess = new KProcess();
        mIwconfigProcess->setEnvironment( "LANG", "C" );
        mIwconfigProcess->setEnvironment( "LC_ALL", "C" );
        *mIwconfigProcess << PATH_IWCONFIG;
        connect( mIwconfigProcess,  SIGNAL( receivedStdout( KProcess*, char*, int ) ),
                 this, SLOT( iwconfigProcessStdout( KProcess*, char*, int ) ) );
        connect( mIwconfigProcess,  SIGNAL( receivedStderr( KProcess*, char*, int ) ),
                 this, SLOT( iwconfigProcessStdout( KProcess*, char*, int ) ) );
        connect( mIwconfigProcess,  SIGNAL( processExited( KProcess* ) ),
                 this, SLOT( iwconfigProcessExited( KProcess* ) ) );

        if ( !mIwconfigProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput ) )
        {
            delete mIwconfigProcess;
            mIwconfigProcess = 0L;
        }
    }
#endif
#endif // Q_OS_FREEBSD

#ifdef PATH_ROUTE
    if ( !mRouteProcess )
    {
        mRouteStdout = QString::null;
        mRouteProcess = new KProcess();
        mRouteProcess->setEnvironment( "LANG", "C" );
        mRouteProcess->setEnvironment( "LC_ALL", "C" );
#ifdef Q_OS_FREEBSD        
        *mRouteProcess << PATH_ROUTE << "-n" << "get" << "default";
#else
        *mRouteProcess << PATH_ROUTE << "-n";
#endif
        connect( mRouteProcess,  SIGNAL( receivedStdout( KProcess*, char*, int ) ),
                 this, SLOT( routeProcessStdout( KProcess*, char*, int ) ) );
        connect( mRouteProcess,  SIGNAL( receivedStderr( KProcess*, char*, int ) ),
                 this, SLOT( routeProcessStdout( KProcess*, char*, int ) ) );
        connect( mRouteProcess,  SIGNAL( processExited( KProcess* ) ),
                 this, SLOT( routeProcessExited( KProcess* ) ) );

        if ( !mRouteProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput ) )
        {
            delete mRouteProcess;
            mRouteProcess = 0L;
        }
    }
#endif

#ifdef Q_OS_FREEBSD
    // Check interfaces and update accordingly

    Interface* interface = 0;
    InterfaceData* data = 0;
    
    struct if_data* ifd;
    struct ifaddrs *ifap;
    char buf[NI_MAXHOST];

    // Reset all devices first
    QDictIterator<Interface> ifIt( mInterfaceDict );
    for ( ; ifIt.current(); ++ifIt )
    {
        interface = ifIt.current();
        interface->getData().existing = false;
        interface->getData().available = false;
        interface->getData().addrData.clear();
    }
   
    // Get IP address and related information
    if ( getifaddrs(&ifap) == 0 ) {
        for ( ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next ) {
            switch ( ifa->ifa_addr->sa_family ) {
                case AF_INET6:
                case AF_INET:
                    interface = mInterfaceDict[QString::fromLatin1(ifa->ifa_name)];
                    if ( interface ) {
                        data = &interface->getData();
                     
                        bzero(buf, NI_MAXHOST);
                        getnameinfo(ifa->ifa_addr, ifa->ifa_addr->sa_len, buf, sizeof(buf), 0, 0, NI_NUMERICHOST);
                        AddrData *addrData = new AddrData;
                        data->addrData.insert( QString::fromLatin1(buf), addrData);

                        if ( ifa->ifa_netmask != NULL ) {
                            if (ifa->ifa_addr->sa_family == AF_INET6 ) {
                                bzero(buf, NI_MAXHOST);
                                getnameinfo(ifa->ifa_netmask, ifa->ifa_netmask->sa_len, buf, sizeof(buf), 0, 0, NI_NUMERICHOST);
                                addrData->subnetMask = QString::fromLatin1( buf );
                            } else {
                                struct sockaddr_in *sin = (struct sockaddr_in *)ifa->ifa_netmask;
                                addrData->subnetMask = QString::fromLatin1( inet_ntoa(sin->sin_addr) );
                            }
                        }

                        if ( ifa->ifa_broadaddr != NULL ) {
                            bzero(buf, NI_MAXHOST);
                            getnameinfo(ifa->ifa_broadaddr, ifa->ifa_broadaddr->sa_len, buf, sizeof(buf), 0, 0, NI_NUMERICHOST);
                            addrData->broadcastAddress = QString::fromLatin1(buf);
                        }
                       
                        if ( ifa->ifa_dstaddr != NULL ) {
                            bzero(buf, NI_MAXHOST);
                            getnameinfo(ifa->ifa_dstaddr, ifa->ifa_dstaddr->sa_len, buf, sizeof(buf), 0, 0, NI_NUMERICHOST);
                            data->ptpAddress = QString::fromLatin1(buf);
                        }
                        
                        data->existing = true;
                        interface->setType((ifa->ifa_flags & IFF_POINTOPOINT ) ? Interface::PPP : Interface::ETHERNET );

                        // Get media status
                        int s;
                        if ((s = socket(ifa->ifa_addr->sa_family, SOCK_DGRAM, 0)) >= 0) {
                            struct ifmediareq ifmr;
                            (void) memset(&ifmr, 0, sizeof(ifmr));
                            (void) strncpy(ifmr.ifm_name, ifa->ifa_name, sizeof(ifmr.ifm_name));

                            if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0) {
                                if (ifmr.ifm_status & IFM_AVALID)
                                     /* 
                                      * The interface is marked available, if the interface is up
                                      * and if it is has carrier (LAN) or is associated (WLAN)
                                      */
                                     data->available = (ifa->ifa_flags & IFF_UP) && (ifmr.ifm_status & IFM_ACTIVE);
                            }
                            else
                                data->available = (ifa->ifa_flags & IFF_UP);
                            
                            close(s);
                        }
                    }
                    break;
                 
                 case AF_LINK:                
                    interface = mInterfaceDict[QString::fromLatin1(ifa->ifa_name)];
                    if ( interface ) {
                        data = &interface->getData();

                        // get MAC address
                        struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifa->ifa_addr;
                        if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == ETHER_ADDR_LEN)
                            data->hwAddress = QString::fromLatin1(ether_ntoa((struct ether_addr*)LLADDR(sdl)));
                        else
                            data->hwAddress = "";
                        
                        // Get traffic statistics
                        if (ifa->ifa_data != NULL) {
                            ifd = (if_data *)ifa->ifa_data;
                            
                            data->rxPackets = ifd->ifi_ipackets;
                            data->txPackets = ifd->ifi_opackets;
                        
                            if ( data->prevRxBytes == 0L )
                                data->prevRxBytes = ifd->ifi_ibytes;
                            else 
                                data->prevRxBytes = data->rxBytes;
                        
                            data->rxBytes = ifd->ifi_ibytes;
                            data->incomingBytes = data->rxBytes - data->prevRxBytes;
                            data->rxString = KIO::convertSize( data->rxBytes );
                    
                            if ( data->prevTxBytes == 0L )
                                data->prevTxBytes = ifd->ifi_obytes;
                            else 
                                data->prevTxBytes = data->txBytes;
                            
                            data->txBytes = ifd->ifi_obytes;
                            data->outgoingBytes = data->txBytes - data->prevTxBytes;
                            data->txString = KIO::convertSize( data->txBytes );
                        }
                    }
                    break;
            }
        }
        freeifaddrs(ifap);
    }

    // Update the display
    for ( ifIt.toFirst(); ifIt.current(); ++ifIt )
        ifIt.current()->activateMonitor();

#endif // Q_OS_FREEBSD
}

void InterfaceUpdater::routeProcessExited( KProcess* process )
{
    if ( process == mRouteProcess )
    {
        mRouteProcess->deleteLater(); // we're in a slot connected to mRouteProcess
        mRouteProcess = 0L;
        parseRouteOutput();
    }
}

void InterfaceUpdater::routeProcessStdout( KProcess*, char* buffer, int buflen )
{
    mRouteStdout += QString::fromLatin1( buffer, buflen );
}

void InterfaceUpdater::ifconfigProcessExited( KProcess* process )
{
    if ( process == mIfconfigProcess )
    {
        delete mIfconfigProcess;
        mIfconfigProcess = 0L;
        parseIfconfigOutput();
    }
}

void InterfaceUpdater::ifconfigProcessStdout( KProcess*, char* buffer, int buflen )
{
    mIfconfigStdout += QString::fromLatin1( buffer, buflen );
}

void InterfaceUpdater::iwconfigProcessExited( KProcess* process )
{
    if ( process == mIwconfigProcess )
    {
        delete mIwconfigProcess;
        mIwconfigProcess = 0L;
        parseIwconfigOutput();
    }
}

void InterfaceUpdater::iwconfigProcessStdout( KProcess*, char* buffer, int buflen )
{
    mIwconfigStdout += QString::fromLatin1( buffer, buflen );
}

void InterfaceUpdater::parseIfconfigOutput()
{
#ifndef Q_OS_FREEBSD    
    /* mIfconfigStdout contains the complete output of 'ifconfig' which we
     * are going to parse here.
     */
    QMap<QString, QString> configs;
    QStringList ifList = QStringList::split( "\n\n", mIfconfigStdout );
    QStringList::Iterator it;
    for ( it = ifList.begin(); it != ifList.end(); ++it )
    {
        int index = ( *it ).find( ' ' );
        if ( index == -1 )
            continue;
        QString key = ( *it ).left( index );
        configs[key] = ( *it ).mid( index );
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'ifconfig'
     * we update its data, otherwise we mark it as
     * 'not existing'.
     */
    QDictIterator<Interface> ifIt( mInterfaceDict );
    for ( ; ifIt.current(); ++ifIt )
    {
        QString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) == configs.end() )
        {
            // The interface does not exist. Meaning the driver
            // isn't loaded and/or the interface has not been created.
            interface->getData().existing = false;
            interface->getData().available = false;
        }
        // JJ 2005-07-18: use RUNNING instead of UP to detect whether interface is connected
        else if ( !configs[key].contains( "inet " ) ||
                  !configs[key].contains( "RUNNING" ) )
        {
            // The interface is up or has an IP assigned but not both
            interface->getData().existing = true;
            interface->getData().available = false;
        }
        else
        {
            // ...determine the type of the interface
            if ( configs[key].contains( "Ethernet" ) )
                interface->setType( Interface::ETHERNET );
            else
                interface->setType( Interface::PPP );

            // Update the interface.
            interface->getData().existing = true;
            interface->getData().available = true;
            updateInterfaceData( configs[key], interface->getData(), interface->getType() );
        }
        interface->activateMonitor();
    }
#endif
}

void InterfaceUpdater::updateInterfaceData( QString& config, InterfaceData& data, int type )
{
#ifndef Q_OS_FREEBSD    
    QRegExp regExp( ".*RX.*:(\\d+).*:\\d+.*:\\d+.*:\\d+" );
    if ( regExp.search( config ) > -1 )
        data.rxPackets = regExp.cap( 1 ).toULong();

    regExp.setPattern( ".*TX.*:(\\d+).*:\\d+.*:\\d+.*:\\d+" );
    if ( regExp.search( config ) > -1 )
        data.txPackets = regExp.cap( 1 ).toULong();

    regExp.setPattern( "RX bytes:(\\d+)\\s*\\(\\d+\\.\\d+\\s*\\w+\\)" );
    if ( regExp.search( config ) > -1 )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        unsigned long currentRxBytes = regExp.cap( 1 ).toULong();
        if ( currentRxBytes < data.prevRxBytes )
        {
            // there was an overflow
            data.rxBytes += 0x7FFFFFFF - data.prevRxBytes;
            data.prevRxBytes = 0L;
        }
        if ( data.rxBytes == 0L )
        {
            // on startup set to currently received bytes
            data.rxBytes = currentRxBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevRxBytes = currentRxBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.rxBytes += currentRxBytes - data.prevRxBytes;

        data.incomingBytes = currentRxBytes - data.prevRxBytes;
        data.prevRxBytes = currentRxBytes;
        data.rxString = KIO::convertSize( data.rxBytes );
    }

    regExp.setPattern( "TX bytes:(\\d+)\\s*\\(\\d+\\.\\d+\\s*\\w+\\)" );
    if ( regExp.search( config ) > -1 )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        unsigned long currentTxBytes = regExp.cap( 1 ).toULong();
        if ( currentTxBytes < data.prevTxBytes )
        {
            // there was an overflow
            data.txBytes += 0x7FFFFFFF - data.prevTxBytes;
            data.prevTxBytes = 0L;
        }
        if ( data.txBytes == 0L )
        {
            // on startup set to currently transmitted bytes
            data.txBytes = currentTxBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevTxBytes = currentTxBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.txBytes += currentTxBytes - data.prevTxBytes;

        data.outgoingBytes = currentTxBytes - data.prevTxBytes;
        data.prevTxBytes = currentTxBytes;
        data.txString = KIO::convertSize( data.txBytes );
    }

    regExp.setPattern( "inet\\s+\\w+:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
    if ( regExp.search( config ) > -1 )
        data.ipAddress = regExp.cap( 1 );

    regExp.setPattern( "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
    if ( regExp.search( config ) > -1 )
    {
        data.broadcastAddress = regExp.cap( 2 );
        data.subnetMask = regExp.cap( 3 );
    }

    if ( type == Interface::ETHERNET )
    {
        regExp.setPattern( "(.{2}:.{2}:.{2}:.{2}:.{2}:.{2})" );
        if ( regExp.search( config ) > -1 )
            data.hwAddress = regExp.cap( 1 );
    }
    else if (  type == Interface::PPP )
    {
        regExp.setPattern( "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
        if ( regExp.search( config ) > -1 )
            data.ptpAddress = regExp.cap( 2 );
    }
#endif
}

void InterfaceUpdater::parseIwconfigOutput()
{
#ifndef Q_OS_FREEBSD
    /* mIwconfigStdout contains the complete output of 'iwconfig' which we
     * are going to parse here.
     */
    QMap<QString, QString> configs;
    QStringList ifList = QStringList::split( "\n\n", mIwconfigStdout );
    QStringList::Iterator it;
    for ( it = ifList.begin(); it != ifList.end(); ++it )
    {
        int index = ( *it ).find( ' ' );
        if ( index == -1 )
            continue;
        QString key = ( *it ).left( index );
        configs[key] = ( *it ).mid( index );
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'iwconfig'
     * we update its data.
     */
    QDictIterator<Interface> ifIt( mInterfaceDict );
    for ( ; ifIt.current(); ++ifIt )
    {
        QString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) == configs.end() )
        {
            // The interface was not found.
            continue;
        }
        else if ( configs[key].contains( "no wireless extensions" ) )
        {
            // The interface isn't a wireless device.
            interface->getData().wirelessDevice = false;
        }
        else
        {
            // Update the wireless data of the interface.
            interface->getData().wirelessDevice = true;
            updateWirelessData( configs[key], interface->getWirelessData() );
        }
    }
#endif
}

void InterfaceUpdater::updateWirelessData( QString& config, WirelessData& data )
{
#ifndef Q_OS_FREEBSD
    QRegExp regExp( "ESSID:\"?([^\"]*)\"?" );
    if ( regExp.search( config ) > -1 )
        data.essid = regExp.cap( 1 );

    regExp.setPattern( "Mode:(\\w*)" );
    if ( regExp.search( config ) > -1 )
        data.mode = regExp.cap( 1 );

    regExp.setPattern( "Frequency:([\\w|\\.]*)" );
    if ( regExp.search( config ) > -1 )
        data.frequency = regExp.cap( 1 );
    else
    {
        regExp.setPattern( "Channel:(\\d*)" );
        if ( regExp.search( config ) > -1 )
            data.channel = regExp.cap( 1 );
    }

    regExp.setPattern( "Bit Rate[=:]([\\w/]*)" );
    if ( regExp.search( config ) > -1 )
        data.bitRate = regExp.cap( 1 );

    regExp.setPattern( "Signal level.(-?\\d+\\s*\\w+)" );
    if ( regExp.search( config ) > -1 )
        data.signal = regExp.cap( 1 );

    regExp.setPattern( "Noise level.(-?\\d+\\s*\\w+)" );
    if ( regExp.search( config ) > -1 )
        data.noise = regExp.cap( 1 );

    regExp.setPattern( "Link Quality[=:]([\\d/]*)" );
    if ( regExp.search( config ) > -1 )
        data.linkQuality = regExp.cap( 1 );
#endif
}

void InterfaceUpdater::parseRouteOutput()
{
    /* mRouteStdout contains the complete output of 'route' which we
     * are going to parse here.
     */
    QMap<QString, QStringList> configs;
    QStringList gateway, routeList = QStringList::split( "\n", mRouteStdout );
    QStringList::Iterator it;
    for ( it = routeList.begin(); it != routeList.end(); ++it )
    {
        QStringList routeParameter = QStringList::split( " ", *it );
#ifdef Q_OS_FREEBSD
        if ( routeParameter.count() != 2 )
            continue;
        if ( routeParameter[0] == "gateway:" )
            gateway = routeParameter;
        if ( routeParameter[0] == "interface:" )
            configs[routeParameter[1]] = gateway;
#else        
        if ( routeParameter.count() < 8 ) // no routing entry
            continue;
        if ( routeParameter[0] != "0.0.0.0" ) // no default route
            continue;
        configs[routeParameter[7]] = routeParameter;
#endif
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'route' we update
     * the data of the interface.
     */
    QDictIterator<Interface> ifIt( mInterfaceDict );
    for ( ; ifIt.current(); ++ifIt )
    {
        QString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) != configs.end() )
        {
            // Update the default gateway.
            QStringList routeParameter = configs[key];
            interface->getData().defaultGateway = routeParameter[1];
        }
        else
        {
            // Reset the default gateway.
            interface->getData().defaultGateway = QString::null;
        }
    }
}

#include "interfaceupdater.moc"


syntax highlighted by Code2HTML, v. 0.9.1