/* Microsoft Reference Implementation for TPM 2.0
 *
 *  The copyright in this software is being made available under the BSD License,
 *  included below. This software may be subject to other third party and
 *  contributor rights, including patent rights, and no such rights are granted
 *  under this license.
 *
 *  Copyright (c) Microsoft Corporation
 *
 *  All rights reserved.
 *
 *  BSD License
 *
 *  Redistribution and use in source and binary forms, with or without modification,
 *  are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this list
 *  of conditions and the following disclaimer.
 *
 *  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 COPYRIGHT HOLDERS AND CONTRIBUTORS ""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 COPYRIGHT HOLDER 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.
 */

#if !defined(_MSC_VER) && defined(USE_PLATFORM_EPS)
#include <stdlib.h>
#include <memory.h>
#include <time.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <libudev.h>
#include <sys/stat.h>

#include "Tpm.h"
#include "Platform_fp.h"

#define DEVICEID_SIZE 48

// This extension is needed as TPM2B_STRING only define TPM2B variable when #GLOBAL_C is defined.
#define TPM2B_STRING_EXTENSION(name, value)                 \
TPM2B_STRING(name, value);                                  \
const TPM2B_##name##_ name##_ = STRING_INITIALIZER(value);  \
const TPM2B *name = &name##_.b

TPM2B_STRING_EXTENSION(EPS_CREATION, "EPS Creation");

// Definition for Device ID value.
TPM2B_TYPE(DEVICEID, DEVICEID_SIZE);
const unsigned int MAC_ADDRESS_MAXIMUM_SIZE = 6;

// This value is used to store device id derived from hardware parameters.
static TPM2B_DEVICEID deviceID = {0};
static BOOL isDeviceIDSet = FALSE;

// Read mac address of the device and copy over to the given buffer.
// Returns 0 for success and -1 for error.

static int GetMacAddress()
{
    struct ifreq interfaceRequest = {0};
    struct ifconf interfaceConfiguration = {0};
    char interfaceConfigurationBuffer[1024] = {0};

    int inetSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (inetSocket == -1)
    {
        return -1;
    }

    interfaceConfiguration.ifc_len = sizeof(interfaceConfigurationBuffer);
    interfaceConfiguration.ifc_buf = interfaceConfigurationBuffer;
    if((ioctl(inetSocket, SIOCGIFCONF, &interfaceConfiguration)) == -1)
    {
        close(inetSocket);
        return -1;
    }

    struct ifreq* intefaceRequestStart = interfaceConfiguration.ifc_req;
    const struct ifreq* const interfaceRequestEnd = intefaceRequestStart + (interfaceConfiguration.ifc_len / sizeof(struct ifreq));

    int32_t result = -1;

    for (; intefaceRequestStart != interfaceRequestEnd; ++intefaceRequestStart)
    {
        strcpy(interfaceRequest.ifr_name, intefaceRequestStart->ifr_name);
        if (ioctl(inetSocket, SIOCGIFFLAGS, &interfaceRequest) == 0)
        {
            // don't count loopback
            if ((interfaceRequest.ifr_flags & IFF_LOOPBACK) == 0)
            {
                if (ioctl(inetSocket, SIOCGIFHWADDR, &interfaceRequest) == 0)
                {
                    result = 0;
                    break;
                }
            }
        }
        else
        {
            break;
        }
    }

    if (result == 0)
    {
        unsigned int size = deviceID.t.size <= MAC_ADDRESS_MAXIMUM_SIZE ? deviceID.t.size : MAC_ADDRESS_MAXIMUM_SIZE;
        memcpy(deviceID.t.buffer, interfaceRequest.ifr_hwaddr.sa_data, size);
    }

    close(inetSocket);
    return result;
}

// Read primary harddisk/emmc disk serial id from device and copy over to the given buffer.
// Returns 0 for success and -1 for error.

static int GetDiskSerialNumber()
{
    struct udev *ud = NULL;
    struct stat statbuf;
    struct udev_device *device = NULL;
    struct udev_list_entry *entry = NULL;
    int result = -1;

    ud = udev_new();
    if (NULL == ud)
    {
        return result;
    }
    else
    {

        const unsigned int diskDeviceNamesSize = 2;
        const char *diskDeviceNames[] = {
            "/dev/sda", // primary hard disk.
            "/dev/mmcblk0" // primary eMMC disk.
        };

        unsigned int i = 0;
        while (i < diskDeviceNamesSize)
        {
            if (0 == stat(diskDeviceNames[i], &statbuf))
            {
                break;
            }
            i++;
        }

        if (i == diskDeviceNamesSize)
        {
            goto Cleanup;
        }

        const char blockDeviceType = 'b';
        device = udev_device_new_from_devnum(ud, blockDeviceType, statbuf.st_rdev);
        if (NULL == device)
        {
            goto Cleanup;
        }
        else
        {
            entry = udev_device_get_properties_list_entry(device);
            while (NULL != entry)
            {
                if (0 == strcmp(udev_list_entry_get_name(entry),
                    "ID_SERIAL"))
                {
                    break;
                }

                entry = udev_list_entry_get_next(entry);
            }

            if(entry == NULL)
            {
                goto Cleanup;
            }

            const char* serialNumber = udev_list_entry_get_value(entry);
            size_t serialNumberLength = strlen(serialNumber);

            size_t dataCopyLength = deviceID.t.size - MAC_ADDRESS_MAXIMUM_SIZE;
            if (serialNumberLength < dataCopyLength)
            {
                dataCopyLength = serialNumberLength;
            }

            memcpy(deviceID.t.buffer, serialNumber, dataCopyLength);

            result = 0;
        }

Cleanup:
        if(device)
        {
            udev_device_unref(device);
        }

        (void)udev_unref(ud);
        return result;
    }
}

#if defined(SIMULATION) && (SIMULATION == YES)
// Get device id from hardware parameters.
// CAUTION: Primary seeds derived from device unique IDs are guaranteed to remain the same as long as the reference
//  implementation manufactures its NV state on the same device. Since this implementation of GetDeviceID() relies
//  solely on publicly accessible values (storage device serial numbers and networking card MAC address), it can
//  only be used for the simulation purposes, as it cannot be used to produce a secret value.
// pre-requisites - assumes that MAC address or disk device (i.e. /dev/sda or /dev/mmcblk0) present on the device.
TPM_RC GetDeviceID()
{
    if(isDeviceIDSet == FALSE)
    {
        if(GetMacAddress() == 0)
        {
            isDeviceIDSet = TRUE;
        }

        if(GetDiskSerialNumber() == 0)
        {
            isDeviceIDSet = TRUE;
        }

        if(isDeviceIDSet == FALSE)
        {
            return TPM_RC_FAILURE;
        }
    }

    return TPM_RC_SUCCESS;
}
#endif

void GetSeed(UINT16 size, uint8_t *seed, const TPM2B *purpose)
{
    RAND_STATE rand;

    TPM_RC result = GetDeviceID();
    if(result != TPM_RC_SUCCESS)
    {
        LOG_FAILURE(FATAL_ERROR_INTERNAL);
        return;
    }

    result = DRBG_InstantiateSeeded(&rand.drbg, &deviceID.b, purpose, NULL, NULL);
    if(result != TPM_RC_SUCCESS)
    {
        LOG_FAILURE(FATAL_ERROR_INTERNAL);
        return;
    }

    if(DRBG_Generate(&rand, seed, size) == 0)
    {
        LOG_FAILURE(FATAL_ERROR_INTERNAL);
    }
    return;
}

void
_plat__GetEPS(
    UINT16 size,
    uint8_t *seed
    )
{
    // Ignore GCC warning.
    (void)EPS_CREATION_;
    GetSeed(size, seed, EPS_CREATION);
}

#endif