/** Creates a STUN request and sends it to a random STUN server selected from
 *  the list stored in the config file. See
 *  https://tools.ietf.org/html/rfc5389#section-6
 *  for details on the message structure.
 *  The request is send through m_transaction_host, from which the answer
 *  will be retrieved by parseStunResponse()
 */
void GetPublicAddress::createStunRequest()
{
    // Pick a random stun server
    std::vector<std::string> stun_servers = UserConfigParams::m_stun_servers;

    const char* server_name = stun_servers[rand() % stun_servers.size()].c_str();
    Log::debug("GetPublicAddress", "Using STUN server %s", server_name);

    struct addrinfo hints, *res;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    hints.ai_socktype = SOCK_STREAM;

    // Resolve the stun server name so we can send it a STUN request
    int status = getaddrinfo(server_name, NULL, &hints, &res);
    if (status != 0)
    {
        Log::error("GetPublicAddress", "Error in getaddrinfo: %s",
                   gai_strerror(status));
        return;
    }
    // documentation says it points to "one or more addrinfo structures"
    assert(res != NULL);
    struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr);
    m_stun_server_ip = ntohl(current_interface->sin_addr.s_addr);

    // Create a new socket for the stun server.
    m_transaction_host = new Network(1, 1, 0, 0);

    // Assemble the message for the stun server
    BareNetworkString s(21);

    // bytes 0-1: the type of the message
    // bytes 2-3: message length added to header (attributes)
    uint16_t message_type = 0x0001; // binding request
    uint16_t message_length = 0x0000;
    s.addUInt16(message_type).addUInt16(message_length)
                             .addUInt32(0x2112A442);
    // bytes 8-19: the transaction id
    for (int i = 0; i < 12; i++)
    {
        uint8_t random_byte = rand() % 256;
        s.addUInt8(random_byte);
        m_stun_tansaction_id[i] = random_byte;
    }
    s.addChar(0);


    m_transaction_host->sendRawPacket(s,
                                      TransportAddress(m_stun_server_ip,
                                                       m_stun_server_port) );
    freeaddrinfo(res);
    m_state = STUN_REQUEST_SENT;
}   // createStunRequest
void GetPublicAddress::asynchronousUpdate()
{
    if (m_state == NOTHING_DONE)
    {
        // format :               00MMMMMCMMMCMMMM (cf rfc 5389)
        uint16_t message_type = 0x0001; // binding request
        m_stun_tansaction_id[0] = stunRand();
        m_stun_tansaction_id[1] = stunRand();
        m_stun_tansaction_id[2] = stunRand();
        uint16_t message_length = 0x0000;

        uint8_t bytes[21]; // the message to be sent
        // bytes 0-1 : the type of the message,
        bytes[0] = (uint8_t)(message_type>>8);
        bytes[1] = (uint8_t)(message_type);

        // bytes 2-3 : message length added to header (attributes)
        bytes[2] = (uint8_t)(message_length>>8);
        bytes[3] = (uint8_t)(message_length);

        // bytes 4-7 : magic cookie to recognize the stun protocol
        bytes[4] = (uint8_t)(m_stun_magic_cookie>>24);
        bytes[5] = (uint8_t)(m_stun_magic_cookie>>16);
        bytes[6] = (uint8_t)(m_stun_magic_cookie>>8);
        bytes[7] = (uint8_t)(m_stun_magic_cookie);

        // bytes 8-19 : the transaction id
        bytes[8] = (uint8_t)(m_stun_tansaction_id[0]>>24);
        bytes[9] = (uint8_t)(m_stun_tansaction_id[0]>>16);
        bytes[10] = (uint8_t)(m_stun_tansaction_id[0]>>8);
        bytes[11] = (uint8_t)(m_stun_tansaction_id[0]);
        bytes[12] = (uint8_t)(m_stun_tansaction_id[1]>>24);
        bytes[13] = (uint8_t)(m_stun_tansaction_id[1]>>16);
        bytes[14] = (uint8_t)(m_stun_tansaction_id[1]>>8);
        bytes[15] = (uint8_t)(m_stun_tansaction_id[1]);
        bytes[16] = (uint8_t)(m_stun_tansaction_id[2]>>24);
        bytes[17] = (uint8_t)(m_stun_tansaction_id[2]>>16);
        bytes[18] = (uint8_t)(m_stun_tansaction_id[2]>>8);
        bytes[19] = (uint8_t)(m_stun_tansaction_id[2]);
        bytes[20] = '\0';

        // time to pick a random stun server
        std::vector<std::string> stun_servers = UserConfigParams::m_stun_servers;

        RandomGenerator random_gen;
        int rand_result = random_gen.get((int)stun_servers.size());
        Log::verbose("GetPublicAddress", "Using STUN server %s",
                     stun_servers[rand_result].c_str());

        // resolve the name into an IP address
        struct addrinfo hints, *res, *p;
        int status;

        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
        hints.ai_socktype = SOCK_STREAM;

        if ((status = getaddrinfo(stun_servers[rand_result].c_str(), NULL, &hints, &res)) != 0) {
            Log::error("getaddrinfo", gai_strerror(status));
            return;
        }
        for(p = res;p != NULL; p = p->ai_next)
        {
            struct sockaddr_in* current_interface = (struct sockaddr_in*)(p->ai_addr);

            m_stun_server_ip = ntohl(current_interface->sin_addr.s_addr);
            m_transaction_host = new STKHost();
            m_transaction_host->setupClient(1,1,0,0);
            m_transaction_host->sendRawPacket(bytes, 20, TransportAddress(m_stun_server_ip, 3478));
            m_state = TEST_SENT;

            freeaddrinfo(res); // free the linked list
            return;
        }
        freeaddrinfo(res); // free the linked list

    }