/*
**  OSSP uuid - Universally Unique Identifier
**  Copyright (c) 2004-2008 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2004-2008 The OSSP Project <http://www.ossp.org/>
**
**  This file is part of OSSP uuid, a library for the generation
**  of UUIDs which can found at http://www.ossp.org/pkg/lib/uuid/
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  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.
**
**  uuid_mac.c: Media Access Control (MAC) resolver implementation
*/

/* own headers (part (1/2) */
#include "config.h"
#define __EXTENSIONS__  // illumos

/* system headers */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_NET_IF_DL_H
#include <net/if_dl.h>
#endif
#ifdef HAVE_NET_IF_ARP_H
#include <net/if_arp.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <winsock2.h>

#include <iphlpapi.h>
#endif

/* own headers (part (1/2) */
#include "uuid_mac.h"

/* IEEE 802 MAC address encoding/decoding bit fields */
#define IEEE_MAC_MCBIT 0b00000001
#define min(a, b) ((a) < (b) ? (a) : (b))


/* return the Media Access Control (MAC) address of the first network interface card (NIC) with a non-multicast address */
bool mac_address(uint8_t * data_ptr) {
#ifdef HAVE_IFADDRS_H
	struct ifaddrs *alloc = NULL, addr = {.ifa_name = "eth0", .ifa_flags = IFF_UP | IFF_BROADCAST}, *itr = &addr;
	if(getifaddrs(&alloc) == -1)
		alloc = NULL;
	else
		itr = alloc;
	bool ret = false;

#if defined(HAVE_NET_IF_DL_H)  // BSD class platforms (xxxBSD, MacOS X, etc)
	for(; itr; itr = itr->ifa_next)
		if(itr->ifa_addr != NULL && itr->ifa_addr->sa_family == AF_LINK) {
			struct sockaddr_dl * sdl = (void *)itr->ifa_addr;
			uint8_t * mac            = (void *)(sdl->sdl_data + sdl->sdl_nlen);

			if(sdl->sdl_alen && !(mac[0] & IEEE_MAC_MCBIT)) {
				memcpy(data_ptr, mac, min(MAC_LEN, sdl->sdl_alen));
				ret = true;
				break;
			}
		}
#endif

#if defined(HAVE_NET_IF_H) && defined(SIOCGIFHWADDR)  // Linux and the illumos gate
	int s = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
	if(s == -1)
		return false;

	for(; itr; itr = itr->ifa_next)
		if((itr->ifa_flags & IFF_UP) && (itr->ifa_flags & IFF_BROADCAST)) {
			struct ifreq ifr;
			strncpy(ifr.ifr_name, itr->ifa_name, sizeof(ifr.ifr_name));
			if(ioctl(s, SIOCGIFHWADDR, &ifr) == -1)
				continue;

			if(ifr.ifr_addr.sa_data[0] & IEEE_MAC_MCBIT)
				continue;
			memcpy(data_ptr, ifr.ifr_addr.sa_data, MAC_LEN);
			ret = true;
			goto ret;
		}

ret:
	close(s);
#endif

	if(alloc)
		freeifaddrs(alloc);
	return ret;
#elif defined(_WIN32)
	IP_ADAPTER_ADDRESSES * alloc = NULL;
	ULONG itr_size               = 0;
	bool ret                     = false;

	for(;;)
		switch(GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_ALL_COMPARTMENTS, NULL, alloc, &itr_size)) {
			case ERROR_SUCCESS:
				for(IP_ADAPTER_ADDRESSES * itr = alloc; itr; itr = itr->Next)
					if(itr->IfType == IF_TYPE_ETHERNET_CSMACD && !(itr->PhysicalAddress[0] & IEEE_MAC_MCBIT)) {
						memcpy(data_ptr, itr->PhysicalAddress, min(MAC_LEN, itr->PhysicalAddressLength));
						ret = true;
						goto ret;
					}
				break;
			case ERROR_BUFFER_OVERFLOW: {
				void * new = realloc(alloc, itr_size);
				if(!new)
					goto ret;
				alloc = new;
			} break;
			default:
				goto ret;
		}

ret:
	free(alloc);
	return ret;
#endif

	return false;
}
