/*
 * This module provides an interface to send and receive ICMP messages
 * which is closer to the way Unix programs are written than the standard
 * WIN32 icmp dll's interface.
 */

/* $Id: icmp_win32.c,v 1.11 1999/10/24 22:47:31 fgouget Exp $ */

/* don't know where it should come form */
#define LITTLE_ENDIAN    0
#define BIG_ENDIAN    1
#define BYTE_ORDER LITTLE_ENDIAN

#include "win32/win32.h"
#include <winsock.h>
#include "win32/types.h"

#include "netinet/ip.h"
#include "netinet/ip_var.h"
#include "netinet/ip_icmp.h"

#include <ipexport.h>
#include <icmpapi.h>

#include "mod_icmp.h"

#include <malloc.h>
#include <errno.h>

typedef struct {
        HANDLE            hICMP;        /* The ICMP dll handle */

        /* "Socket" options */
        int            rcvbufsize;    /* Size of the receive buffer */
        struct ip_option_information ip_options;/* The IP options */
        unsigned long        timeout;    /* Time to wait for a reply */

        /* A few things to remember about the request */
        u_short            msg_id;
        u_short            msg_seq;

        /* Some fields to process the answers */
        struct icmp_echo_reply* rcvbuf;        /* The buffer in which */
                            /* IcmpSendEcho will store the answers */
        int            nb_replies;    /* -1 => message not sent yet. */
                                    /* >=0 => number of reply messages */
        struct icmp_echo_reply*    current;    /* Pointer to next reply */

        LARGE_INTEGER rtt_in_ticks;        /* This is our own measurment of the RTT */
        LARGE_INTEGER ticks_freq;
    } icmp_state_i;

#define handle2state(h)        ((icmp_state_i*)h)

icmp_handle icmp_open()
{
    icmp_state_i* state;

    /* Give a higher priority so that bing has better 
     * chances not to be delayed when measuring the RTT.
     */
    SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS);

    /* Perform some initialisation to ease error recovery */
    state=NULL;

    /* Allocate the state */
    state=malloc(sizeof(icmp_state_i));

    /* Fill defaults */
    state->hICMP=IcmpCreateFile();
    state->rcvbufsize=4096;
    state->rcvbuf=NULL;
    state->nb_replies=-1;
    QueryPerformanceFrequency(&state->ticks_freq);

    /* Set options defaults */
    state->ip_options.Ttl=255;
    state->ip_options.Tos=0;
    state->ip_options.Flags=0;
    state->ip_options.OptionsSize=0;
    state->ip_options.OptionsData=NULL;
    state->timeout=2000;
    return (icmp_handle)state;
}

int icmp_set_option(icmp_handle handle, int level, int optname, char* optval,
                           int optlen)
{
    icmp_state_i* state;
    int ret;

    ret=0;
    state=handle2state(handle);
    switch (level) {
    case IPPROTO_IP:
        /* The IP options are handled by building the 
         * corresponding IP structure by hand.
         */
        switch (optname) {
        case IP_TTL:
            state->ip_options.Ttl=*((int*)optval);
            break;
        case IP_TOS:
            state->ip_options.Tos=*((int*)optval);
            break;
        /*(!!)case IP_FLAGS:
            state->ip_options.Flags=*((int*)optval);
            break;*/
        case IP_OPTIONS:
            if (optlen>state->ip_options.OptionsSize) {
                state->ip_options.OptionsSize=optlen;
                state->ip_options.OptionsData=realloc(state->ip_options.OptionsData,optlen);
                if (state->ip_options.OptionsData==NULL) {
                    errno=ENOMEM;
                    ret=-1;
                    goto end;
                }
                memcpy(state->ip_options.OptionsData,(char*)optval,state->ip_options.OptionsSize);
            }
            break;
        default:
            errno=ENOSYS;
            ret=-1;
            break;
        }
        break;
    case SOL_SOCKET:
        switch (optname) {
        case SO_RCVBUF:
            state->rcvbufsize=*((int*)optval);
            break;
        default:
            errno=ENOSYS;
            ret=-1;
            break;
        }
        break;
    default:
        errno=ENOSYS;
        ret=-1;
    }

end:
    return ret;
}

void icmp_set_timeout(icmp_handle handle,unsigned long timeout)
{
    handle2state(handle)->timeout=timeout/1000;
}

unsigned short icmp_get_id(icmp_handle handle)
{
    return handle2state(handle)->msg_id;
}

int icmp_send(icmp_handle handle, char* msg, int msg_size, 
              struct sockaddr* to_addr, int to_addr_size)
{
    icmp_state_i* state;
    LARGE_INTEGER start,stop;

    state=handle2state(handle);

    /* Record some information for the recv */
    state->msg_id=((struct icmp*)msg)->icmp_id;
    state->msg_seq=((struct icmp*)msg)->icmp_seq;

    /* allocate the buffer for the replies */
    state->rcvbuf=realloc(state->rcvbuf,
        state->rcvbufsize);

    QueryPerformanceCounter(&start);
    state->nb_replies=IcmpSendEcho(state->hICMP,
                *((IPAddr*)&(((struct sockaddr_in*)to_addr)->sin_addr)),
                msg+ICMP_MINLEN,
                (WORD)(msg_size-ICMP_MINLEN),
                &state->ip_options,
                state->rcvbuf,
                state->rcvbufsize,
                state->timeout
            );
    QueryPerformanceCounter(&stop);

    if ((state->ticks_freq.QuadPart!=0) && 
        (state->nb_replies==1)) {
        /* If we have a high performance counter, use it to measure 
         * the RTT. The high performance counter will either give us 
         * a much more precise measure of the RTT than the ICMP 
         * library or it will give us a gross exageration of the RTT 
         * if the execution of our process has been delayed by the 
         * scheduler. Statistically this should give much better 
         * results than the ICMP library (which tends to sometimes 
         * under-estimate the RTT by up to nearly 2 ms which is 
         * BAD in our case.
         */
        state->rtt_in_ticks.QuadPart=stop.QuadPart-start.QuadPart;
    } else
        state->rtt_in_ticks.QuadPart=0;

    if ((state->nb_replies==0) && (GetLastError()!=IP_REQ_TIMED_OUT)) {
        printf("icmp_send: error %d\n",GetLastError());/* (!!) printf !!! */
        state->nb_replies=-1;
        errno=GetLastError();
        return -1;
    }
    state->current=state->rcvbuf;
    return msg_size;
}

int icmp_recv(icmp_handle handle, char* buffer, int buffer_size,
              struct sockaddr* from_addr, int* from_addr_size, 
              double *elapsed)
{
    icmp_state_i* state;
    int ip_header_len;
    struct ip* ip_msg;
    struct icmp* icmp_msg;

    state=handle2state(handle);

    if (state->nb_replies<0) {
        /* icmp_send has not been called yet, or it failed */
        return -1;
    }
    if (state->nb_replies==0) {
        /* We have not more results to return */
        return 0;
    }

    if (buffer_size<sizeof(struct ip)+state->current->Options.OptionsSize+ICMP_MINLEN+state->current->DataSize) {
        /* We cannot return the result because the buffer is too small */
        errno=EFAULT;
        return -1;
    }

    /* Misc return values */
    ((struct sockaddr_in*)from_addr)->sin_family=AF_INET;
    ((struct sockaddr_in*)from_addr)->sin_port=0;
    memcpy(&(((struct sockaddr_in*)from_addr)->sin_addr),&state->current->Address,4);
    if (state->rtt_in_ticks.QuadPart==0)
        *elapsed=(double)(state->current->RoundTripTime);
    else
        *elapsed=((double)(state->rtt_in_ticks.QuadPart*1000))/
            state->ticks_freq.QuadPart;

    /* Reconstruct the ip header */
    ip_header_len=sizeof(struct ip)+state->current->Options.OptionsSize;
    ip_msg=(struct ip*)buffer;
    ip_msg->ip_v=4;
    ip_msg->ip_hl=ip_header_len >> 2;
    ip_msg->ip_tos=state->current->Options.Tos;
    ip_msg->ip_len=ip_header_len+state->current->DataSize;
    ip_msg->ip_id=0;
    ip_msg->ip_off=0;
    ip_msg->ip_ttl=state->current->Options.Ttl;
    ip_msg->ip_p=IPPROTO_ICMP;
    ip_msg->ip_sum=0;
    memcpy(&(ip_msg->ip_src),&state->current->Address,4);
    ip_msg->ip_dst.S_un.S_addr=inet_addr("127.0.0.1");
    if (state->current->Options.OptionsSize>0)
        memcpy(buffer+sizeof(struct ip),
            state->current->Options.OptionsData,
            state->current->Options.OptionsSize);

    /* Reconstruct the icmp header */
    icmp_msg=(struct icmp*)(buffer+ip_header_len);
    switch (state->current->Status) {
    /* Echo Reply (what we expect) */
    case IP_SUCCESS:
        icmp_msg->icmp_type=ICMP_ECHOREPLY;
        icmp_msg->icmp_code=0;
        break;

    /* Destination Unreachable */
    case IP_DEST_NET_UNREACHABLE:
        icmp_msg->icmp_type=ICMP_UNREACH;
        icmp_msg->icmp_code=ICMP_UNREACH_NET;
        break;
    case IP_DEST_HOST_UNREACHABLE:
        icmp_msg->icmp_type=ICMP_UNREACH;
        icmp_msg->icmp_code=ICMP_UNREACH_HOST;
        break;
    case IP_DEST_PROT_UNREACHABLE:
        icmp_msg->icmp_type=ICMP_UNREACH;
        icmp_msg->icmp_code=ICMP_UNREACH_PROTOCOL;
        break;
    case IP_DEST_PORT_UNREACHABLE:
        icmp_msg->icmp_type=ICMP_UNREACH;
        icmp_msg->icmp_code=ICMP_UNREACH_PORT;
        break;
    case IP_BAD_ROUTE:
        icmp_msg->icmp_type=ICMP_UNREACH;
        icmp_msg->icmp_code=ICMP_UNREACH_SRCFAIL;
        break;

    /* Time Exceeded */
    case IP_TTL_EXPIRED_TRANSIT:
        icmp_msg->icmp_type=ICMP_TIMXCEED;
        icmp_msg->icmp_code=ICMP_TIMXCEED_INTRANS;
        break;
    case IP_TTL_EXPIRED_REASSEM:
        icmp_msg->icmp_type=ICMP_TIMXCEED;
        icmp_msg->icmp_code=ICMP_TIMXCEED_REASS;
        break;

    /* Parameter Problem */
    case IP_PARAM_PROBLEM:
        icmp_msg->icmp_type=ICMP_PARAMPROB;
        icmp_msg->icmp_code=0;
        /* how can I get a value for the pointer field ? */
        break;

    /* Source quench */
    case IP_SOURCE_QUENCH:
        icmp_msg->icmp_type=ICMP_SOURCEQUENCH;
        icmp_msg->icmp_code=0;
        break;

    default:
        handle2state(handle)->nb_replies--;
        handle2state(handle)->current++;
        return handle2state(handle)->current->Status;
    }
    if (state->current->Status==IP_SUCCESS) {
        icmp_msg->icmp_id=state->msg_id;
        icmp_msg->icmp_seq=state->msg_seq;
    } else {
        icmp_msg->icmp_id=0;
        icmp_msg->icmp_seq=0;
    }

    /* Retrieve the data (includes the icmp header) */
    memcpy(buffer+ip_header_len+ICMP_MINLEN,
        state->current->Data,
        state->current->DataSize);

    state->nb_replies--;
    state->current++;
    return ip_msg->ip_len;
}

int icmp_close(icmp_handle handle)
{
    int ret;

    ret=IcmpCloseHandle((HANDLE)(((icmp_state_i*)handle)->hICMP));
    free(handle);
    return (ret?0:-1);
}
