/*
 * Copyright (c) 2017, 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 <winsock2.h>
#include <Ws2tcpip.h>
#include <stdlib.h>
#include <stdarg.h>
#include <locale.h>
#include <sys/types.h>
#include <time.h>
#include <iphlpapi.h>
#include <openssl/evp.h>
#include <process.h>
#pragma comment(lib, "IPHLPAPI.lib")


#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0

#define GOTO_ERR(condition)                             \
    do {                                                \
        if (condition) {                                \
            log_msg("Operation failed");                \
            goto error;                                 \
        }                                               \
    } while(0)

/**
 * maximum length of trusted assets store property
 */
#define TAM_MAX_PROP_SIZE 256

#define UTF8_ENCODING "UTF-8"
static char* platform_encoding = NULL;

#define MAC_ADDRESS_LENGTH 18

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

/*
 * To have a simple 1 byte version and be readable outside of the blob,
 * the first version number will be first printable ASCII character 33 (!).
 *
 * Since the last printable char is 126 (~), we can have 94 versions.
 */
#define DEFAULT_FORMAT_VERSION 33

/*
 * if "trustAnchors" property is equal to this string then system default CA store
 * must be used for host certificate validation */
#define SYSTEM_DEFAULT_CA "SYSTEM_DEFAULT_CA"

/*
 * if "trustAnchors" property is equal to this string then CA store is in the same
 * file below between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----
 */
#define IN_PLACE_CA "IN_PLACE_CA"

/**
 * multicast socket receive buffer size
 */
#define REQUEST_RECEIVE_BUF_SIZE 4096

/**
 * Message type: Network Provisioner to Bootstrapper - request for client identification information.
 */
#define DISCOVER_REQUEST 0x01

/*
 * To have a simple 1 byte version and be readable outside of the blob,
 * the first version number will be first printable ASCII character 33 (!).
 *
 * Since the last printable char is 126 (~), we can have 94 versions.
 */
#define DEFAULT_FORMAT_VERSION 33

/**
 * Message type: Bootstrapper to Network Provisioner - client identification information.
 */
#define DISCOVER_RESPONSE 0x02

/**
 * Message type: Network Provisioner to Bootstrapper - provisioning information
 */
#define PROVISION_REQUEST 0x03

/**
 * Message type: Bootstrapper to Network Provisioner - response provisioning status.
 */
#define PROVISION_RESPONSE 0x04

/**
 * Provision status: Successful result
 */
#define STATUS_SUCCESS 0x00

/**
 * Provision status: Failed result
 */
#define STATUS_FAILURE 0x01

/**
 * Provision status: Unknown result
 */
#define STATUS_UNKNOWN 0x02

/**
 * The address to which the Network Provisioner sends multicast messages.
 */
#define MULTICAST_ADDRESS "238.163.7.96"

/**
 * multicast socket send buffer size
 */
#define REQUEST_SEND_BUF_SIZE 1024

/**
 * The port to listen on for messages from the Network Provisioner.
 */
#define UDP_PORT 4456

/**
 * Holds data for processing trusted assets store file
 */
typedef struct trusted_assets_store_t {
    // trusted assets store file name obtained from taStore bootstrapper arg
    char* ta_store_name;
    // trusted assets store password obtained from taStorePassword bootstrapper arg
    char* ta_store_pwd;
    // trusted assets store properties needed to complete provisioning
    char client_id[TAM_MAX_PROP_SIZE];
    char shared_secret[TAM_MAX_PROP_SIZE];
    char trust_store_type[TAM_MAX_PROP_SIZE];
    char server_host[TAM_MAX_PROP_SIZE];
    char server_port[TAM_MAX_PROP_SIZE];
    char server_scheme[TAM_MAX_PROP_SIZE];
    char signature[TAM_MAX_PROP_SIZE];
} trusted_assets_store;

/**
 * Return codes for bootstrapper functions
 */
typedef enum {
    BS_RESULT_OK = 0x0, /** Operation succeded */
    BS_RESULT_FAIL = 0x1, /** Operation failed */
} bs_result;

#define OLD_TRUSTED_ASSETS_STORE_FORMAT 0
#define UNIFIED_TRUSTED_ASSETS_STORE_FORMAT 1

/**
 * Secret key length for AES/ECB/PKCS5Padding is 128 bit = 16 bytes
 */
#define SECRET_KEY_LENGTH 16

/**
 * Maximum value of LV length
 */
#define MAX_LV_LENGTH 255

/**
 * Type of trusted assets store
 * 0 - old
 * 1 - unified
 */
static int tas_type = OLD_TRUSTED_ASSETS_STORE_FORMAT;

/**
 * Writes formatted message to stdout
 * @param message the message to be printed
 */
void log_msg(const char* message, ...) {
    va_list arg;

    va_start(arg, message);
    vfprintf(stdout, message, arg);
    fprintf(stdout, "\n");
    va_end(arg);
}

#define PROP_FORMAT_SIZE 128
static char property_format[PROP_FORMAT_SIZE];
static char line_buffer[PROP_FORMAT_SIZE];

/**
 * Holds length value pair (LV), where length is unsigned less then MAX_LV_LENGTH, 
 * value is string length bytes long
 */
typedef struct length_value_t {
    size_t length;
    char* value;
} length_value;

static int size_t_to_int(size_t val) {
    return (val <= INT_MAX) ? (int) val : -1;
}

#define  IP_ADDRESS_IS_NOT_ZERO(address_list) \
    (address_list).IpAddress.String[0] && _strnicmp((address_list).IpAddress.String, "0.0.0.0", 7)

/**
 * @brief Writes mac address of the of the given network adapter as a null terminated string to a given buffer
 * @param buffer output buffer
 * @param buf_len length of the output buffer
 * @param iface network adapter
 * @return The number of characters written to \a buffer, not counting the terminating null character.
 * @return Negative value otherwise (including cases when \a buffer is too small for the string)
 */
static int get_interface_mac_address_string(char* buffer, int buffer_length, PIP_ADAPTER_INFO iface) {
    GOTO_ERR(!iface || !buffer || buffer_length <= 0);
    GOTO_ERR(iface->AddressLength == 0);
    GOTO_ERR(size_t_to_int(iface->AddressLength) < 0);
    GOTO_ERR((int) iface->AddressLength > buffer_length / 3);
    /* Assuming that iface->AddressLength > 0*/
    GOTO_ERR(size_t_to_int(iface->Address[0]) < 0);
    GOTO_ERR(0 >= sprintf(buffer, "%.2X", (int) iface->Address[0]));
    for (int i = 1; i < (int) iface->AddressLength; i++) {
        GOTO_ERR(size_t_to_int(iface->Address[i]) < 0);
        GOTO_ERR(0 >= sprintf(buffer + 3 * i - 1, "-%.2X", (int) iface->Address[i]));
    }
    /* null termination */
    buffer[3 * iface->AddressLength - 1] = 0;
    return 3 * iface->AddressLength - 1;
error:
    return -1;
}

/**
 * searches for an adapter which has IPv4 & gateway addresses both non equal to zero
 */
static PIP_ADAPTER_INFO lookup_main_network_interface(PIP_ADAPTER_INFO first) {
    PIP_ADAPTER_INFO pAdapter = first;
    while (pAdapter != NULL) {

        /**
         * skip local loopback
         */
        switch (pAdapter->Type) {
            case MIB_IF_TYPE_LOOPBACK:
                continue;
            default:
                ;
        };
        /**
         * consider the main adapter the one has both ipv4 address and gateway address non equal to zero
         */
        if (IP_ADDRESS_IS_NOT_ZERO(pAdapter->IpAddressList) &&
                IP_ADDRESS_IS_NOT_ZERO(pAdapter->GatewayList)) {
            return pAdapter;
        }
        pAdapter = pAdapter->Next;
    }
    return NULL;
}

/**
 * returns the network adapters list as a continuous memory chunk
 */
static PIP_ADAPTER_INFO list_network_interfaces() {
    PIP_ADAPTER_INFO pAdapterInfo = NULL;
    ULONG ulOutBufLen = 0;
    DWORD dwRetVal = 0;

    /* retrieving size of buffer required for adapters linked list */
    GOTO_ERR(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_BUFFER_OVERFLOW);
    GOTO_ERR(NULL == (pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen)));
    /* retrieving size linked list of adapters */
    if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) != NO_ERROR) {
        free(pAdapterInfo);
        pAdapterInfo = NULL;
    }
error:
    return pAdapterInfo;
}

/**
 * Obtains the MAC address and stores it to mac_address parameter
 * @param mac_address mac address buffer to be set
 * @param mac_address_len the buffer length
 */
static void set_mac_address(char* mac_address, size_t mac_address_len) {
    PIP_ADAPTER_INFO adapters = list_network_interfaces();
    if (adapters != NULL) {
        PIP_ADAPTER_INFO iface = lookup_main_network_interface(adapters);
        if (iface != NULL) {
            get_interface_mac_address_string(mac_address, mac_address_len, iface);
        }
        free(adapters);
    }
}

static bs_result read_property(FILE* file, const char* prop_name, char* prop_value) {
    GOTO_ERR(NULL == fgets(line_buffer, TAM_MAX_PROP_SIZE, file));
    GOTO_ERR(0 >= snprintf(property_format, PROP_FORMAT_SIZE, "%s=%%%us", prop_name, TAM_MAX_PROP_SIZE - 1));
    GOTO_ERR(0 == sscanf(line_buffer, property_format, prop_value));
    log_msg("    %s=%s", prop_name, prop_value);
    return BS_RESULT_OK;
error:
    log_msg("Failed to read %s from trusted assets store", prop_name);
    return BS_RESULT_FAIL;
}

#define PARSE_ARG(arg, arg_name, arg_value, arg_index, is_mandatory) \
    if (strlen(arg_name) < strlen(arg) && \
        0 == strncmp(arg_name, arg, strlen(arg_name))) { \
        arg_value = arg + strlen(arg_name) + 1; \
        arg_index++; \
    } \
    else if (is_mandatory) { \
        log_msg("No " arg_name " parameter specified"); \
        return BS_RESULT_FAIL; \
    }

/**
 * bootstrapper argument names
 */
#define TRUSTED_ASSETS_STORE_ARG "taStore"
#define TRUSTED_ASSETS_STORE_PASSWORD_ARG "taStorePassword"

/**
 * Parses and checks bootstrapper arguments, saves trusted assets store file name 
 * and password to the corresponding fields of store struct and sets 
 * device_app_arg_index to the index of the device application in argv
 * @param argc number of arguments passed to bootstrapper
 * @param argv array of the arguments
 * @param store trusted assets store
 * @param device_app_arg_index index of the device application in argv to be set
 * by parse_args
 * @return BS_RESULT_OK if the arguments are valid, BS_RESULT_FAIL otherwise
 */
static bs_result parse_args(int argc, char** argv,
        trusted_assets_store* store, int* device_app_arg_index) {
    int index = 1;
    if (argc < 3)
        return BS_RESULT_FAIL;
    PARSE_ARG(argv[index], TRUSTED_ASSETS_STORE_ARG, store->ta_store_name, index, 1);
    PARSE_ARG(argv[index], TRUSTED_ASSETS_STORE_PASSWORD_ARG, store->ta_store_pwd, index, 1);
    *device_app_arg_index = index;
    return BS_RESULT_OK;
}

/**
 * Converts a sequence of characters in input_encoding encoding to a sequence 
 * of characters in output_encoding encoding. If succeeds, sets output_buf_len 
 * to the number of bytes stored in the output buffer
 * @param input_encoding encoding of input data
 * @param input_buf input data buffer
 * @param input_buf_len the number of bytes in input buffer
 * @param output_encoding encoding of result
 * @param output_buf output buffer
 * @param output_buf_len the number of bytes [IN] available / [OUT] stored in the 
 * output buffer
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result encode(
        const char* input_encoding, char* input_buf, size_t input_buf_len,
        const char* output_encoding, char* output_buf, size_t* output_buf_len) {
    if (0 == strcmp(input_encoding, output_encoding)) {
        //no encoding required
        if (input_buf_len <= *output_buf_len) {
            strncpy(output_buf, input_buf, input_buf_len);
            *output_buf_len = input_buf_len;
        } else {
            log_msg("encoding buffer overflow");
            return BS_RESULT_FAIL;
        }
    } else {
        //not including null-terminated symbol
        size_t requiredSize = mbstowcs(NULL, input_buf, input_buf_len);
        WCHAR* tmp = (WCHAR*) calloc(requiredSize + 1, sizeof (WCHAR));

        if (!tmp) {
            log_msg("Out of memory error was handled.");
            return BS_RESULT_FAIL;
        }

        requiredSize = mbstowcs(tmp, input_buf, input_buf_len);
        requiredSize = WideCharToMultiByte(CP_UTF8, 0, tmp, -1,
                NULL, 0, NULL, NULL);

        if (requiredSize > *output_buf_len) {
            log_msg("Output buffer isn't enough. Required %d Current: %d",
                    requiredSize, *output_buf_len);
            free(tmp);
            return BS_RESULT_FAIL;
        }
        *output_buf_len = WideCharToMultiByte(CP_UTF8, 0, tmp, -1,
                output_buf, requiredSize, NULL, NULL);
        free(tmp);
    }
    return BS_RESULT_OK;
}

/**
 * Encodes length value pair to UTF-8 and stores it to the output buffer. If 
 * succeeds, sets output_buf_len to the number of bytes stored in the output 
 * buffer.
 * @param lv input length value
 * @param output_buf output buffer
 * @param output_buf_len the number of bytes [IN] available / [OUT] stored in the 
 * output buffer
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result encode_length_value(length_value* lv,
        char* output_buf, size_t* output_buf_len) {
    size_t encoded_len = *output_buf_len - 1;
    if (lv->length > MAX_LV_LENGTH) {
        log_msg("LV length exceeds maximum value of 255");
        return BS_RESULT_FAIL;
    }
    if (BS_RESULT_OK != encode(platform_encoding, lv->value, lv->length,
            UTF8_ENCODING, output_buf + 1, &encoded_len)) {
        return BS_RESULT_FAIL;
    } else {
        output_buf[0] = encoded_len;
        *output_buf_len = encoded_len + 1;
        return BS_RESULT_OK;
    }
}

/**
 * Encodes array of length value pairs to UTF-8 and stores it sequentially to 
 * the output buffer. If succeeds, sets output_buf_len to the number of bytes 
 * stored in the output buffer.
 * @param lvs input array of length values
 * @param lvs_len length of the lvs array
 * @param output_buf output buffer
 * @param output_buf_len the number of bytes [IN] available / [OUT] stored in the 
 * output buffer
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result encode_length_values(length_value* lvs, size_t lvs_len,
        char* output_buf, size_t* output_buf_len) {
    size_t offset = 0, encoded_len, i;
    for (i = 0; i < lvs_len; i++) {
        encoded_len = *output_buf_len - offset;
        if (BS_RESULT_OK != encode_length_value(&lvs[i], output_buf + offset, &encoded_len)) {
            *output_buf_len = 0;
            return BS_RESULT_FAIL;
        }
        offset += encoded_len;
    }
    *output_buf_len = offset;
    return BS_RESULT_OK;
}

/**
 * Reads trusted assets store file and stores property values to the
 * corresponding fields of store struct
 * @param store trusted asset store to be filled by values from the file
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result read_trusted_assets_store(trusted_assets_store* store) {
    FILE* store_file;
    int version;
    log_msg("Reading trusted asset file at %s", store->ta_store_name);

    store_file = fopen(store->ta_store_name, "r");
    if (store_file == NULL) {
        log_msg("No trusted assets file found");
        return BS_RESULT_FAIL;
    }

    GOTO_ERR(EOF == (version = fgetc(store_file)));

    if (version != DEFAULT_FORMAT_VERSION) {
        tas_type = OLD_TRUSTED_ASSETS_STORE_FORMAT;
        rewind(store_file); /* put back first character */
        GOTO_ERR(read_property(store_file, "client.id", store->client_id));
        GOTO_ERR(read_property(store_file, "client.secret", store->shared_secret));
        GOTO_ERR(read_property(store_file, "trustAnchors", store->trust_store_type));

        if (0 == strcmp(store->trust_store_type, IN_PLACE_CA)) {
            GOTO_ERR(0 >= snprintf(property_format, PROP_FORMAT_SIZE, "%s=%%%us", "server.host", TAM_MAX_PROP_SIZE - 1));

            /* skip cert - everything between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- */
            do {
                GOTO_ERR(NULL == fgets(line_buffer, PROP_FORMAT_SIZE, store_file));
            } while (0 != strncmp(line_buffer, "server.host", strlen("server.host")));
            GOTO_ERR(sscanf(line_buffer, property_format, store->server_host) != 1);
            log_msg("    server.host=%s", store->server_host);
        } else {
            if (0 != strcmp(store->trust_store_type, SYSTEM_DEFAULT_CA)) {
                log_msg("'trustAnchors' property has invalid value: expected " SYSTEM_DEFAULT_CA " or " IN_PLACE_CA);
                goto error;
            }
            GOTO_ERR(read_property(store_file, "server.host", store->server_host));
        }

        GOTO_ERR(read_property(store_file, "server.port", store->server_port));
        GOTO_ERR(read_property(store_file, "server.scheme", store->server_scheme));
        GOTO_ERR(0 >= snprintf(property_format, PROP_FORMAT_SIZE, "%s=%%%us", "signature", TAM_MAX_PROP_SIZE - 1));

        /* skip indirect connected data*/
        do {
            GOTO_ERR(NULL == fgets(line_buffer, PROP_FORMAT_SIZE, store_file));
        } while (0 != strncmp(line_buffer, "signature", strlen("signature")));
        GOTO_ERR(sscanf(line_buffer, property_format, store->signature) != 1);
        log_msg("    signature=%s", store->signature);
    } else {
        tas_type = UNIFIED_TRUSTED_ASSETS_STORE_FORMAT;
        log_msg("Trusted assets store file has unified form. It means that it should be fully provisioned.");
    }

    fclose(store_file);
    return BS_RESULT_OK;

error:
    fclose(store_file);
    return BS_RESULT_FAIL;
}

/**
 * Sends the discovery response message.
 * @param socket_fd descriptor of socket on which to send the response
 * @param dest_addr the address to send response to
 * @param dest_addr_len dest_addr size
 */
static void handle_discover_request(int socket_fd,
        struct sockaddr_in* dest_addr, socklen_t dest_addr_len) {
    char buffer[REQUEST_SEND_BUF_SIZE];
    size_t buffer_len = REQUEST_SEND_BUF_SIZE;
    size_t offset = 0;
    buffer[offset++] = DISCOVER_RESPONSE;

    char mac_address[MAC_ADDRESS_LENGTH];
    size_t mac_address_len = MAC_ADDRESS_LENGTH;
    set_mac_address(mac_address, mac_address_len);

    // Change this code if MAC address is not desired as the device information
    length_value lvs[] = {
        {3, "MAC"},
        {mac_address_len, mac_address}
    };

    buffer_len -= offset;
    if (BS_RESULT_OK != encode_length_values(lvs, ARRAY_SIZE(lvs),
            buffer + offset, &buffer_len))
        return;
    buffer_len += offset;

    if (-1 == sendto(socket_fd, buffer, buffer_len, 0,
            (SOCKADDR *) dest_addr, dest_addr_len)) {
        perror("Failed to send discover response");
    }
}

/**
 * Sends the result status response message
 * @param socket_fd descriptor of socket on which to send the response
 * @param dest_addr the address to send response to
 * @param dest_addr_len dest_addr size
 * @param status the response status to send
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result send_provision_response(int socket_fd,
        struct sockaddr_in* dest_addr, socklen_t dest_addr_len,
        char status) {
    char plain_data[] = {status};
    int plain_data_len = ARRAY_SIZE(plain_data);
    char data[ARRAY_SIZE(plain_data) + EVP_MAX_BLOCK_LENGTH];

    char buffer[ARRAY_SIZE(data) + 1];
    size_t buf_len = 0;

    buffer[0] = PROVISION_RESPONSE;
    memcpy(buffer + 1, plain_data, plain_data_len);
    buf_len = plain_data_len + 1;

    if (buf_len > 0 &&
            -1 == sendto(socket_fd, buffer, buf_len, 0,
            (SOCKADDR *) dest_addr, dest_addr_len)) {
        perror("Failed to send provision response");
        return BS_RESULT_FAIL;
    }
    return BS_RESULT_OK;
}

/**
 * Initializes multicast socket used for communication with Network Provisioner
 * @param socket_fd socket descriptor to be set
 * @return BS_RESULT_OK if operation succeeds, BS_RESULT_FAIL otherwise
 */
static bs_result init_multicast_socket(SOCKET* socket_fd) {
    WSADATA wsaData;
    struct sockaddr_in mc_addr;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        log_msg("Failed. Error Code : %d", WSAGetLastError());
        exit(EXIT_FAILURE);
    }

    //Create a socket
    if ((*socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
        log_msg("Could not create socket : %d", WSAGetLastError());
        return BS_RESULT_FAIL;
    }

    memset(&mc_addr, 0, sizeof (mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = INADDR_ANY;
    mc_addr.sin_port = htons(UDP_PORT);

    if (bind(*socket_fd, (struct sockaddr *) &mc_addr, sizeof (mc_addr)) == SOCKET_ERROR) {
        perror("bind() failed");
        closesocket(*socket_fd);
        WSACleanup();
        return BS_RESULT_FAIL;
    }

    int enable = 1;

    // set reuse port to on to allow multiple binds per host
    if (setsockopt(*socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof (enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        closesocket(*socket_fd);
        WSACleanup();
        return BS_RESULT_FAIL;
    }

    // construct join request structure
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_ADDRESS);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    // send an ADD MEMBERSHIP message via setsockopt
    if (setsockopt(*socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof (mreq)) < 0) {
        perror("setsockopt(IP_ADD_MEMBERSHIP) failed");
        closesocket(*socket_fd);
        WSACleanup();
        return BS_RESULT_FAIL;
    }
    return BS_RESULT_OK;
}

/**
 * Finalizes multicast socket used for communication with Network Provisioner
 * @param socket_fd socket descriptor
 */
static void finalize_multicast_socket(SOCKET socket_fd) {
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_ADDRESS);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    // send a DROP MEMBERSHIP message via setsockopt
    if (setsockopt(socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*) &mreq, sizeof (mreq)) < 0) {
        perror("setsockopt(IP_DROP_MEMBERSHIP) failed");
    }
    closesocket(socket_fd);
    WSACleanup();
}

/**
 * Displays the correct usage of bootstrapper
 */
static void show_usage(void) {
    log_msg("usage: bootstrapper\n"
            "    " TRUSTED_ASSETS_STORE_ARG "=<trusted assets store file name>\n"
            "    " TRUSTED_ASSETS_STORE_PASSWORD_ARG "=<trusted assets store password>\n"
            "    <application_name> [<app_arg_0>...<app_arg_N>]");
}

int main(int argc, char** argv) {
    trusted_assets_store store;
    int device_app_arg_index;

    if (BS_RESULT_OK != parse_args(argc, argv, &store, &device_app_arg_index)) {
        show_usage();
        return EXIT_FAILURE;
    }

    // init platform encoding
    platform_encoding = setlocale(LC_ALL, "");

    if (BS_RESULT_OK != read_trusted_assets_store(&store)) {
        SOCKET socket_fd;
        if (BS_RESULT_OK != init_multicast_socket(&socket_fd)) {
            return EXIT_FAILURE;
        }

        char* buffer = (char*) malloc(sizeof (char) * REQUEST_RECEIVE_BUF_SIZE);
        if (!buffer) {
            log_msg("Out of memory error handled.");
            finalize_multicast_socket(socket_fd);
            return EXIT_FAILURE;
        }

        intptr_t buffer_len;
        struct sockaddr_in from_addr;
        socklen_t from_addr_len;
        log_msg("Waiting for notification from provisioner");
        while (1) {
            from_addr_len = sizeof (from_addr);
            // block waiting to receive a multicast packet
            fflush(stdout);
            if (0 > (buffer_len = recvfrom(socket_fd, buffer, REQUEST_RECEIVE_BUF_SIZE, 0,
                    (SOCKADDR *) & from_addr, &from_addr_len))) {
                perror("recvfrom() failed");
                finalize_multicast_socket(socket_fd);
                free(buffer);
                return EXIT_FAILURE;
            }
            if (buffer_len == 0)
                continue;
            // Discover or Provision?
            if (buffer[0] == DISCOVER_REQUEST) {
                handle_discover_request(socket_fd, &from_addr, from_addr_len);
            } else if (buffer[0] == PROVISION_REQUEST) {
                FILE* tas_file = fopen(store.ta_store_name, "w");

                if (!tas_file) {
                    log_msg("Can't create %s file.", store.ta_store_name);
                    send_provision_response(socket_fd, &from_addr, from_addr_len,
                            STATUS_FAILURE);
                    finalize_multicast_socket(socket_fd);
                    free(buffer);
                    return EXIT_FAILURE;
                }

                int value_length = (buffer[1] << 8) | buffer[2];

                if (value_length > buffer_len - 3) {
                    log_msg("Not enough of memory. Expected: %d Read to buffer: %d. Please"
                            " increase REQUEST_RECEIVE_BUF_SIZE.", value_length, buffer_len - 3);
                    send_provision_response(socket_fd, &from_addr, from_addr_len,
                            STATUS_FAILURE);
                    finalize_multicast_socket(socket_fd);
                    fclose(tas_file);
                    free(buffer);
                    return EXIT_FAILURE;
                }

                (0 < fwrite(&buffer[3], sizeof (char), buffer_len - 3, tas_file)) ?
                        send_provision_response(socket_fd, &from_addr, from_addr_len,
                        STATUS_SUCCESS) :
                        send_provision_response(socket_fd, &from_addr, from_addr_len,
                        STATUS_FAILURE);
                fclose(tas_file);
                break;
            } else {
                log_msg("Unknown request received");
            }
        }
        finalize_multicast_socket(socket_fd);
        free(buffer);
    }

    // Start the device application
    log_msg("Starting device application %s...", argv[device_app_arg_index]);
    if (-1 == _execv(argv[device_app_arg_index],
            // the first argument should point to the filename being executed
            &argv[device_app_arg_index])) {
        log_msg("Start of device application '%s' failed", argv[device_app_arg_index]);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

