#include <glib.h>
#include <gcrypt.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>

#include "identifier.h"

void
whoopsie_hex_to_char (char* buf, const char *str, int len)
{
    char* p = NULL;
    int i = 0;

    g_return_if_fail (buf);
    g_return_if_fail (str);

    p = buf;
    for (i = 0; i < len; i++) {
        snprintf(p, 3, "%02x", (unsigned char) str[i]);
        p += 2;
    }
    buf[2*len] = 0;
}

void
whoopsie_identifier_get_mac_address (char** res, GError** error)
{
    struct ifreq ifr;
    struct ifconf ifc;
    char buf[1024];
    gboolean success = FALSE;
    int sock;
    struct ifreq* it;
    int i = 0;

    g_return_if_fail (res);

    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock == -1) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                     "Could not create socket.");
        return;
    }

    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = buf;
    if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                     "Could not get list of interface addresses.");
        close (sock);
        return;
    }

    it = ifc.ifc_req;

    for (i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; it++) {
        strcpy(ifr.ifr_name, it->ifr_name);
        if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
            if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
                if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
                    success = TRUE;
                    break;
                }
            }
        }
    }

    close (sock);

    if (!success)
        return;

    *res = g_malloc (18);
    sprintf (*res, "%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]);
}

void
whoopsie_identifier_get_system_uuid (char** res, GError** error)
{
    int fp;

    g_return_if_fail (res);

    fp = open ("/sys/class/dmi/id/product_uuid", O_RDONLY);
    if (fp < 0) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                     "Could not open the product uuid file.");
        return;
    }
    *res = g_malloc (37);
    if (read (fp, *res, 36) == 36)
        (*res)[36] = '\0';
    else
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                     "Got an unexpected length reading the product_uuid.");
    close (fp);
}

void
whoopsie_identifier_sha512 (char* source, char* res, GError** error)
{
    int md_len;
    gcry_md_hd_t sha512 = NULL;
    unsigned char* id = NULL;

    g_return_if_fail (source);
    g_return_if_fail (res);

    if (!gcry_check_version (GCRYPT_VERSION)) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                     "libcrypt version mismatch.");
        return;
    }
    gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
    md_len = gcry_md_get_algo_dlen(GCRY_MD_SHA512);
    if (md_len != HASHLEN / 2) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
                 "Received an incorrect size for the SHA512 message digest");
        return;
    }
    if (gcry_md_open (&sha512, GCRY_MD_SHA512, 0) || sha512 == NULL) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
             "Failed to create a SHA512 message digest for the product_uuid.");
        return;
    }
    gcry_md_write (sha512, source, strlen (source));
    gcry_md_final (sha512);
    id = gcry_md_read (sha512, GCRY_MD_SHA512);
    if (id == NULL) {
        g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
             "Failed to read the SHA512 message digest for the product_uuid.");
        gcry_md_close (sha512);
        return;
    }
    whoopsie_hex_to_char (res, (const char*)id, md_len);
    gcry_md_close (sha512);
}

void
whoopsie_identifier_generate (char** res, GError** error)
{
    char* identifier = NULL;

    g_return_if_fail (res);

    whoopsie_identifier_get_system_uuid (&identifier, error);
    if ((!error || !(*error)) && identifier)
        goto out;

    if (error && *error) {
        g_error_free (*error);
        *error = NULL;
    }

    whoopsie_identifier_get_mac_address (&identifier, error);
    if ((!error || !(*error)) && identifier)
        goto out;

    return;

out:
    *res = g_malloc (HASHLEN + 1);
    whoopsie_identifier_sha512 (identifier, *res, error);
    g_free (identifier);
}

