// [License]
// The Ariba-Underlay Copyright
//
// Copyright (c) 2008-2009, Institute of Telematics, Universität Karlsruhe (TH)
//
// Institute of Telematics
// Universität Karlsruhe (TH)
// Zirkel 2, 76128 Karlsruhe
// Germany
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE INSTITUTE OF TELEMATICS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ARIBA PROJECT OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation
// are those of the authors and should not be interpreted as representing
// official policies, either expressed or implied, of the Institute of
// Telematics.
// [License]

#include "AddressDiscovery.h"
#include "ariba/config.h"

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

#include <string>
#include <boost/asio/ip/address.hpp>
#include <boost/foreach.hpp>

#include "ariba/utility/addressing2/tcpip_endpoint.hpp"
#include "ariba/utility/addressing/mac_address.hpp"

#ifdef HAVE_LIBBLUETOOTH
  #include <bluetooth/bluetooth.h>
  #include <bluetooth/hci.h>
  #include <bluetooth/hci_lib.h>
#endif

namespace ariba {
namespace communication {


using namespace std;
using namespace addressing2;
using namespace boost::asio::ip;

using ariba::addressing::mac_address;

mac_address getMacFromIF( const char* name )
{
	mac_address addr;
#ifdef HAVE_LIBBLUETOOTH
	int s;
	struct ifreq buffer;
	s = socket(PF_INET, SOCK_DGRAM, 0);
	memset(&buffer, 0x00, sizeof(buffer));
	strcpy(buffer.ifr_name, name);
	ioctl(s, SIOCGIFHWADDR, &buffer);
	close(s);
	addr.assign( (uint8_t*)buffer.ifr_hwaddr.sa_data, 6 );
#endif
	return addr;
}

int dev_info(int s, int dev_id, long arg)
{
#ifdef HAVE_LIBBLUETOOTH
//	endpoint_set* set = (endpoint_set*)arg;
//	struct hci_dev_info di;
//	memset(&di, 0, sizeof(struct hci_dev_info));
//	di.dev_id = dev_id;
//	if (ioctl(s, HCIGETDEVINFO, (void *) &di)) return 0;
//	mac_address mac;
//	mac.bluetooth( di.bdaddr );
//	address_vf vf = mac;
//	set->add(vf);
#endif
	return 0;
}

void discover_bluetooth(
        EndpointSetPtr listenOn_endpoints,
        EndpointSetPtr discovered_endpoints )
{
#ifdef HAVE_LIBBLUETOOTH
    // FIXME aktuell bluetooth
//	hci_for_each_dev(HCI_UP, &AddressDiscovery::dev_info, (long)&endpoints );
#endif
}

void discover_ip_addresses(
        EndpointSetPtr listenOn_endpoints,
        EndpointSetPtr discovered_endpoints )
{
    bool discover_ipv4 = false;
    bool discover_ipv6 = false;
    vector<uint16_t> ipv4_ports;
    vector<uint16_t> ipv6_ports;
    
    /* analyze listenOn_endpoints */
    BOOST_FOREACH( TcpIP_EndpointPtr endp, listenOn_endpoints->get_tcpip_endpoints() )
    {
        // BRANCH: IPv4 any [0.0.0.0]
        if ( endp->to_asio().address() == address_v4::any() )
        {
            // add port
            ipv4_ports.push_back(endp->to_asio().port());
            
            discover_ipv4 = true;
        }

        // BRANCH: IPv6 any [::]
        else if ( endp->to_asio().address() == address_v6::any() )
        {
            // add port
            ipv6_ports.push_back(endp->to_asio().port());
            
            discover_ipv6 = true;

            
            // NOTE: on linux the ipv6-any address [::] catches ipv4 as well
            ipv4_ports.push_back(endp->to_asio().port());
            discover_ipv4 = true;
        }
        
        // BRANCH: explicit ip address
        else
        {
            // ---> don't discover anything, just add it directly
            discovered_endpoints->add_endpoint(endp);
        }
    }
    
    
    /* discover addresses */
    if ( discover_ipv4 || discover_ipv6 )
    {
        struct ifaddrs* ifaceBuffer = NULL;
        void*           tmpAddrPtr  = NULL;
    
        int ret = getifaddrs( &ifaceBuffer );
        if( ret != 0 ) return;
    
        for( struct ifaddrs* i=ifaceBuffer; i != NULL; i=i->ifa_next )
        {
            // ignore devices that are disabled or have no ip
            if(i == NULL) continue;
            struct sockaddr* addr = i->ifa_addr;
            if (addr==NULL) continue;
    
    //		// ignore tun devices  // XXX why?
    //		string device = string(i->ifa_name);
    //		if(device.find_first_of("tun") == 0) continue;

            // IPv4
            if ( discover_ipv4 && addr->sa_family == AF_INET )
            {
                char straddr[INET_ADDRSTRLEN];
                tmpAddrPtr= &((struct sockaddr_in*)addr)->sin_addr;
                inet_ntop( i->ifa_addr->sa_family, tmpAddrPtr, straddr, sizeof(straddr) );
                
                address ip_addr = address::from_string(straddr);
    
                // skip loopback address
                if ( ip_addr.to_v4() == address_v4::loopback() )
                    continue;

                // add endpoint for this address and every given ipv4 port
                BOOST_FOREACH( uint16_t port, ipv4_ports )
                {
                    tcp::endpoint tcpip_endp(ip_addr, port);
                    TcpIP_EndpointPtr endp(new tcpip_endpoint(tcpip_endp));
                    
                    discovered_endpoints->add_endpoint(endp);
                }
            }
            
            // IPv6
            else if ( discover_ipv6 && addr->sa_family == AF_INET6 )
            {
                // look for ipv6
                char straddr[INET6_ADDRSTRLEN];
                tmpAddrPtr= &((struct sockaddr_in6*)addr)->sin6_addr;
                inet_ntop( i->ifa_addr->sa_family, tmpAddrPtr, straddr, sizeof(straddr) );
                
                address ip_addr = address::from_string(straddr);
                
                // skip loopback address
                if ( ip_addr.to_v6() == address_v6::loopback() )
                    continue;

                // add endpoint for this address and every given ipv4 port
                BOOST_FOREACH( uint16_t port, ipv6_ports )
                {
                    tcp::endpoint tcpip_endp(ip_addr, port);
                    TcpIP_EndpointPtr endp(new tcpip_endpoint(tcpip_endp));
                    
                    discovered_endpoints->add_endpoint(endp);
                }
            }
        }
    
        freeifaddrs(ifaceBuffer);
    }
}



EndpointSetPtr AddressDiscovery::discover_endpoints(EndpointSetPtr listenOn_endpoints)
{
    EndpointSetPtr discovered_endpoints(new addressing2::endpoint_set());
    
	discover_ip_addresses( listenOn_endpoints, discovered_endpoints );
	discover_bluetooth( listenOn_endpoints, discovered_endpoints );
	
	return discovered_endpoints;
}

}} // namespace ariba, communication
