/*
 * 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.
 */

/**
 * The bootstrapper application is used to complete the provisioning for a 
 * not-provisioned client. With the trusted assets store name and the 
 * trusted assets store password provided through arguments, the bootstrapper 
 * checks to see if the asset store is already provisioned or not. In the 
 * case it is already fully provisioned, the bootstrapper goes ahead and starts 
 * the sample application identified in the third argument. It passes all 
 * subsequent arguments on to the sample application during invocation. 
 * 
 * In the case the client isn't fully provisioned, the bootstrapper 
 * joins the UDP multicast group at the predefined address 
 * MULTICAST_ADDRESS and port UDP_PORT to wait for a message 
 * from the Network Provisioner for discovery or provisioning. If the message 
 * received is for discovery DISCOVER_REQUEST, the bootstrapper sends 
 * back the client information. By default, the information is the client's MAC 
 * address. This information will be displayed by the Network Provisioner to the 
 * operator to select the target client. The information is sent as a key value 
 * pair, where key="MAC" and value is the MAC address. If different information 
 * is desired from the client, the handle_discover_request method should be 
 * modified to send the desired data. 
 * 
 * If the message received is for provisioning PROVISION_REQUEST, the 
 * bootstrapper parses the information sent from the Network Provisioner to get 
 * the serverHost, deviceId, and sharedSecret. These values are used to complete 
 * the provisioning. The result of the provisioning is sent back to the Network 
 * Provisioner. Bootstrapper uses trusted_asset_provisioner.sh utility script to
 * generate trusted assets store. TRUSTED_ASSET_PROVISIONER should contain path 
 * to the script. If the provisioning was successful, the bootstrapper continues 
 * to start the sample application as described previously. If the provisioning 
 * was unsuccessful, the bootstrapper waits until another provisioning attempt 
 * is made. 
 * 
 * The provisioning information is in the unified provisioner format. This 
 * ensures that the provisioning data is sent to the Bootstrapper in encrypted 
 * form. 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <net/if.h>

#if defined(IOTCS_HAVE_GETIFADDRS)
#include <net/if_dl.h>
#include <ifaddrs.h>
#endif

#include <netinet/in.h>
#include <arpa/inet.h>
#include <langinfo.h>
#include <locale.h>
#include <iconv.h>
#include <openssl/evp.h>
#include <errno.h>

/**
 * Constants are used for communication between bootstrapper and Network Provisioner
 */

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

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

/*
 * 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"

/**
 * 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

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

/**
 * path to the trusted_asset_provisioner.sh script
 */
#define TRUSTED_ASSET_PROVISIONER "./trusted_asset_provisioner.sh"

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

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

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

/**
 * 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

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


/**
 * Special value for client.id, client.secret and server.port properties, which 
 * is used in partially pre-provisioned trusted assets stores
 */
#define PARTIAL_PROVISIONED_STR "no"

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

/**
 * 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;

/**
 * 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

/**
 * 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;

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

#define MAC_ADDRESS_LENGTH 18

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

/**
 * 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[TAM_MAX_PROP_SIZE];

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;
}

/**
 * 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;
}

#define STORE_ARG(buffer, buffer_size, param_name, param_value) \
    {\
        int res = snprintf(buffer, buffer_size, param_name "=%s", param_value); \
        if (res < 0 || res >= buffer_size) { \
            log_msg("%s argument initialization failed", param_name); \
            return BS_RESULT_FAIL; \
        } \
    }

/**
 * 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 {
        iconv_t cd;
        size_t output_buf_max_len = *output_buf_len;
        if ((iconv_t) - 1 == (cd = iconv_open(input_encoding, output_encoding))) {
            perror("encoding initialization failed");
            return BS_RESULT_FAIL;
        }
        if ((size_t) - 1 == iconv(cd, &input_buf, &input_buf_len, &output_buf, output_buf_len)) {
            perror("encoding failed");
            iconv_close(cd);
            return BS_RESULT_FAIL;
        }
        iconv_close(cd);
        *output_buf_len = output_buf_max_len - *output_buf_len;
    }
    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;
}

/**
 * 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) {
#if defined(IOTCS_HAVE_SIOCGIFHWADDR)
    struct ifreq ifr;
    struct ifconf ifc;
    char buf[1024];
    int success = 0;

    int socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (-1 != socket_fd) {
        ifc.ifc_len = sizeof (buf);
        ifc.ifc_buf = buf;
        if (-1 != ioctl(socket_fd, SIOCGIFCONF, &ifc)) {
            struct ifreq* it = ifc.ifc_req;
            const struct ifreq * const end = it + (ifc.ifc_len / sizeof (struct ifreq));
            for (; it != end; ++it) {
                strcpy(ifr.ifr_name, it->ifr_name);
                if (ioctl(socket_fd, SIOCGIFFLAGS, &ifr) == 0 &&
                        !(ifr.ifr_flags & IFF_LOOPBACK) /* skip loopback */ &&
                        ioctl(socket_fd, SIOCGIFHWADDR, &ifr) == 0) {
                    unsigned char* mac = (unsigned char*) ifr.ifr_hwaddr.sa_data;
                    snprintf(mac_address, mac_address_len,
                            "%02x:%02x:%02x:%02x:%02x:%02x",
                            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
                    success = 1;
                    break;
                }
            }
        }
    }
    if (!success) {
        log_msg("Obtaining mac address failed");
        strncpy(mac_address, "Unknown", mac_address_len - 1);
    }
    close(socket_fd);
#elif defined(IOTCS_HAVE_GETIFADDRS)
    struct ifaddrs *if_addrs = NULL;
    struct ifaddrs *if_addr = NULL;
    void *tmp = NULL;
    if (0 == getifaddrs(&if_addrs)) {    
        for (if_addr = if_addrs; if_addr != NULL; if_addr = if_addr->ifa_next) {
            if (if_addr->ifa_addr != NULL && if_addr->ifa_addr->sa_family == AF_LINK) {
                struct sockaddr_dl* sdl = (struct sockaddr_dl *)if_addr->ifa_addr;
                if (6 == sdl->sdl_alen) {
                    unsigned char * ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)(if_addr)->ifa_addr);
                    snprintf(mac_address, mac_address_len,
                            "%02x:%02x:%02x:%02x:%02x:%02x",
                            *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5));
                    freeifaddrs(if_addrs);
                    return;
                }
            }
        }
        freeifaddrs(if_addrs);
        if_addrs = NULL;
    } else {
        log_msg("getifaddrs() failed with errno =  %i %s\n", errno, strerror(errno));
        strncpy(mac_address, "Unknown", mac_address_len - 1);
    }
#else
#   error no definition for set_mac_address() on this platform!
#endif
}

/**
 * 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,
            (struct sockaddr *) dest_addr, dest_addr_len)) {
        perror("Failed to send discover response");
    }
}

/**
 * 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(int* socket_fd) {
    // create socket to join multicast group on
    if ((*socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket() failed");
        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, &enable, sizeof (enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        close(*socket_fd);
        return BS_RESULT_FAIL;
    }

    struct sockaddr_in mc_addr;
    // construct a multicast address structure
    memset(&mc_addr, 0, sizeof (mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    mc_addr.sin_port = htons(UDP_PORT);

    // bind to multicast address
    if (bind(*socket_fd, (struct sockaddr *) &mc_addr, sizeof (mc_addr)) < 0) {
        perror("bind() failed");
        close(*socket_fd);
        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, &mreq, sizeof (mreq)) < 0) {
        perror("setsockopt(IP_ADD_MEMBERSHIP) failed");
        close(*socket_fd);
        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(int 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, &mreq, sizeof (mreq)) < 0) {
        perror("setsockopt(IP_DROP_MEMBERSHIP) failed");
    }
    close(socket_fd);
}

/**
 * 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>]");
}

#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; \
    }

/**
 * 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;
}

/**
 * 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,
            (struct sockaddr *) dest_addr, dest_addr_len)) {
        perror("Failed to send provision response");
        return BS_RESULT_FAIL;
    }
    return BS_RESULT_OK;
}

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
    setlocale(LC_ALL, "");
    platform_encoding = nl_langinfo(CODESET);

    if (BS_RESULT_OK != read_trusted_assets_store(&store)) {
        int 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;
        }
        ssize_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
            if (0 > (buffer_len = recvfrom(socket_fd, buffer, REQUEST_RECEIVE_BUF_SIZE, 0,
                    (struct 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;
}