/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/utsname.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <time.h>


// Non POSIX includes (if exist)

// if using gcc version 5 and greater
#ifdef __has_include
#if __has_include("iotcs_port_diagnostic.h") && !__has_include(<non_existing_header>)
// __has_include() do work
#define __HAS_HAS_INCLUDE_
#endif
#endif
#if defined __HAS_HAS_INCLUDE_
#if __has_include(<sys/sysinfo.h>)
#define __HAS_SYSINFO_
#endif
#if __has_include(<net/if_dl.h>)
#define __HAS_IF_DL_
#endif
#if __has_include(<sys/ioctl.h>)
#define __HAS_IOCTL_
#endif
#if __has_include(<sys/sysctl.h>)
#define __HAS_SYSCTL_
#endif
#if __has_include(<ifaddrs.h>)
#define __HAS_IFADDRS_
#endif
#else
// For cases when __has_include is not defined and
// systems where __has_include is defined but doesn't work
#if defined(__linux__) || defined(__CYGWIN__)
#define __HAS_SYSINFO_
#define __HAS_IFADDRS_
#define __HAS_IOCTL_
#elif defined(__bsdi__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__)
#define __HAS_SYSCTL_
#define __HAS_IFADDRS_
#define __HAS_IOCTL_
#define __HAS_IF_DL_
#endif
#endif

#if defined(__HAS_SYSINFO_)
#include <sys/sysinfo.h>
#endif
#if defined(__HAS_IF_DL_)
#include <net/if_dl.h>
#endif
#if defined(__HAS_SYSCTL_)
#include <sys/sysctl.h>
#endif
#if defined(__HAS_IFADDRS_)
#include <ifaddrs.h>
#endif
#if defined(__HAS_IOCTL_)
#include <sys/ioctl.h>
#endif

#include "iotcs_port_system.h"
#include "util/util_memory.h"
#include "iotcs_port_diagnostic.h"
#include "util/util.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PORT_DIAG
#include "log/log_template.h"

#ifndef IOTCS_IFC_BUFFER_LENGTH
#define IOTCS_IFC_BUFFER_LENGTH 1024
#endif

#ifndef IOTCS_MAC_48_ADDRESS_LEN
/** 
 * MAC-48 addresses is six groups of two hexadecimal digits (12), separated by hyphens (5) 
 * don't forget about null symbol (17 + 1)
 */
#define IOTCS_MAC_48_ADDRESS_LEN 18
#endif

#if defined(__HAS_IFADDRS_)

int iotcs_port_get_ip_address(char* buffer, int buf_len) {
    struct ifaddrs *ifap, *ifa;
    struct sockaddr_in *sa;

    char* ip_addr = NULL;

    if (getifaddrs(&ifap) == -1) {
        return -1;
    }

    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL) {
            continue;
        }
        // not local, not loopback, is running
        if (!(ifa->ifa_flags & IFF_RUNNING) || (ifa->ifa_flags & IFF_LOOPBACK)) {
            continue;
        }
        if (ifa->ifa_addr->sa_family == AF_INET) {
            sa = (struct sockaddr_in *) ifa->ifa_addr;
            ip_addr = inet_ntoa(sa->sin_addr);
            // check for 0.0.0.0 and empty ip 
            if ((sa->sin_addr.s_addr != 0) && (strcmp(ip_addr, "") != 0)) {
                break;
            }
        }
    }
    freeifaddrs(ifap);

    if (!ip_addr) {
        /* No ip4 */
        return -1;
    }

    GOTO_ERR_MSG(!buffer || buf_len <= 0, "Invalid parameters");
    GOTO_ERR_MSG(strnlen(ip_addr, buf_len) >= buf_len, "Buffer length is too small for ip4 address.");

    return util_safe_snprintf(buffer, buf_len, "%s", ip_addr);

error:
    return -1;
}
#else
#warning iotcs_port_get_ip_address is not implemented properly, stub implementation is used
int iotcs_port_get_ip_address(char* buffer, int buf_len) {
    GOTO_ERR_MSG(1, "Not implemented");
    error: return -1;
}
#endif

#if defined(__HAS_SYSINFO_) && defined(__HAS_IOCTL_)
int iotcs_port_get_mac_address(char* buffer, int buf_len) {
    struct ifreq ifr;
    struct ifreq *IFR;
    struct ifconf ifc;
    char* buf = NULL;
    int s, i;
    int status = 0;
    int written = -1;

    GOTO_ERR_MSG(!buffer || buf_len <= 0, "Invalid parameters");
    GOTO_ERR_MSG(buf_len < IOTCS_MAC_48_ADDRESS_LEN, "Buffer length is too small for mac address");
    GOTO_ERR_CRIT_MSG(NULL == (buf = (char*) util_malloc(IOTCS_IFC_BUFFER_LENGTH)), "Out of memory");
    GOTO_ERR_MSG(-1 == (s = socket(AF_INET, SOCK_DGRAM, 0)), "socket(0 method failed");

    ifc.ifc_len = IOTCS_IFC_BUFFER_LENGTH;
    ifc.ifc_buf = buf;
    ioctl(s, SIOCGIFCONF, &ifc);

    IFR = ifc.ifc_req;
    for (i = ifc.ifc_len / sizeof (struct ifreq); --i >= 0; IFR++) {
        strcpy(ifr.ifr_name, IFR->ifr_name);
        if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) {
            if (!(ifr.ifr_flags & IFF_LOOPBACK) && ifr.ifr_flags & IFF_RUNNING) {
                    if (ioctl(s, SIOCGIFHWADDR, &ifr) == 0) {
                    status = 1;
                    break;
                }
            }
        }
    }

    GOTO_ERR(!status);
    written = util_safe_snprintf(buffer, buf_len, "%02X:%02X:%02X:%02X:%02X:%02X",
            (unsigned char) ifr.ifr_hwaddr.sa_data[0],
            (unsigned char) ifr.ifr_hwaddr.sa_data[1],
            (unsigned char) ifr.ifr_hwaddr.sa_data[2],
            (unsigned char) ifr.ifr_hwaddr.sa_data[3],
            (unsigned char) ifr.ifr_hwaddr.sa_data[4],
            (unsigned char) ifr.ifr_hwaddr.sa_data[5]);

error:
    close(s);
    util_free(buf);
    return written;
}
#elif defined(__HAS_IF_DL_) && defined(__HAS_IFADDRS_)
int iotcs_port_get_mac_address(char* buffer, int buf_len) {
    int status = 0;
    int written = -1;

    struct ifaddrs* iflist;
    struct ifaddrs* ifr;

    GOTO_ERR_MSG(!buffer || buf_len <= 0, "Invalid parameters");
    GOTO_ERR_MSG(buf_len < IOTCS_MAC_48_ADDRESS_LEN, "Buffer length is too small for mac address");

    GOTO_ERR(getifaddrs(&iflist));
    for (ifr = iflist; ifr; ifr = ifr->ifa_next) {
        if (!(ifr->ifa_flags & IFF_LOOPBACK) && (ifr->ifa_flags & IFF_RUNNING)) {
            if ((ifr->ifa_addr->sa_family == AF_LINK) && ifr->ifa_addr) {
                status = 1;
                break;
            }
        }
    }
    if(status) {
        struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifr->ifa_addr;
        char macaddr[6];
        memcpy(macaddr, LLADDR(sdl), sdl->sdl_alen);
        written = util_safe_snprintf(buffer, buf_len, "%02X:%02X:%02X:%02X:%02X:%02X",
            (unsigned char) macaddr[0],
            (unsigned char) macaddr[1],
            (unsigned char) macaddr[2],
            (unsigned char) macaddr[3],
            (unsigned char) macaddr[4],
            (unsigned char) macaddr[5]);
    }
    freeifaddrs(iflist);
    error:
    return written;
}
#else
#warning iotcs_port_get_mac_address is not implemented properly, stub implementation is used
int iotcs_port_get_mac_address(char* buffer, int buf_len) {
    GOTO_ERR_MSG(1, "Not implemented");
    error: return -1;
}
#endif

#if defined(__HAS_SYSINFO_)
int64_t iotcs_port_get_uptime(void) {
    struct sysinfo s_info;
    if (sysinfo(&s_info) == 0) {
        // time in milliseconds
        return IOTCS_SEC_TO_MILLISEC(s_info.uptime);
    } else {
        return -1;
    }
}
#elif defined(__HAS_SYSCTL_)
int64_t iotcs_port_get_uptime(void) {
    struct timeval uptime;
    size_t uptimeLen = sizeof(uptime);
    int mib[2] = { CTL_KERN, KERN_BOOTTIME };
    if( sysctl(mib, 2, &uptime, &uptimeLen, 0, 0) == 0 )
    {
        // time in milliseconds
        int64_t boottime = IOTCS_SEC_TO_MILLISEC(uptime.tv_sec) + IOTCS_MICROSEC_TO_MILLISEC(uptime.tv_usec);
        return iotcs_port_get_current_time_millis() - boottime;
    } else {
        return -1;
    }
}
#else
#warning iotcs_port_get_uptime is not implemented properly, stub implementation is used
int64_t iotcs_port_get_uptime(void) {
    GOTO_ERR_MSG(1, "Not implemented");
    error: return -1;
}
#endif

int64_t iotcs_port_get_start_time(void) {
    int64_t uptime = iotcs_port_get_uptime();
    if (uptime > 0) {
        // time in milliseconds
        return iotcs_port_get_current_time_millis() - uptime;
    } else {
        return -1;
    }
}

const char* iotcs_port_get_version(void) {
    return "C-Posix Library 1.1 release";
}

uint64_t iotcs_port_get_total_disk_space(void) {
    struct statvfs info;

    if (statvfs("/", &info) != -1) {
        return (uint64_t) (info.f_frsize * info.f_blocks);
    }

    return (uint64_t) - 1;
}

uint64_t iotcs_port_get_free_disk_space(void) {
    struct statvfs info;

    if (statvfs("/", &info) != -1) {
        return (uint64_t) (info.f_frsize * info.f_bfree);
    }
    return (uint64_t) - 1;
}
