/*
 * Copyright (c) 2002 Compaq Computer Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * X-2	    Ankan		Anders Ahgren			30-Mar-2003
 *	    Almost a complete rewrite. We're now interfacing with an
 *	    execlet, which gives us access to the VCI interface.
 *
 * X1.0	    Ankan		Anders Ahgren			29-Nov-2002
 *	    Initial version.
 */
// VMS Includes
#include <errno.h>
#include <ctype.h>                        /* Character type classification macros/routines */ 
#include <descrip.h>                      /* For VMS descriptor manipulation */ 
#include <iodef.h>                        /* I/O function code definitions */ 
#include <ssdef.h>                        /* System service return status code definitions */ 
#include <starlet.h>                      /* System library routine prototypes */ 
#include <stdio.h>                        /* ANSI C Standard Input/Output */ 
#include <stdlib.h>                       /* General utilities */ 
#include <string.h>                       /* String handling */ 
#include <stsdef.h>                       /* VMS status code definitions */ 
#include <efndef.h>
#include <lib$routines.h>
#include <unistd.h>
#include <socket.h>
#include <in.h>
#include <if.h>
#include <ldrimgdef.h>
#include <ldr_routines.h>
#include <nmadef.h>			  /* NMA stuff */
 
#define LANSIZE 256

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "pcapvci.h"
#include "pcap-vms.h"

#include <string.h>

#ifdef HAVE_OS_PROTO_H
#include "os-proto.h"
#endif

#include "pcap-int.h"

#define $SUCCESS(status) (((status) & STS$M_SUCCESS) == SS$_NORMAL) 
#define $FAIL(status) (((status) & STS$M_SUCCESS) != SS$_NORMAL) 
#define init_desc(name, len, addr) \
    { \
    name.dsc$b_dtype = DSC$K_DTYPE_T; \
    name.dsc$b_class = DSC$K_CLASS_S; \
    name.dsc$a_pointer = addr; \
    name.dsc$w_length = len; \
    }


typedef struct _iosb                             
{ 
    short cond_val;                        /* Completion Status */ 
    short size;                  /* Transfer Size */ 
    short addl;                       /* Additional status */ 
    short misc;                       /* Miscellaneous */ 
} IOSB;

typedef struct _interface {
    char interface[4];
    char device[6];
} INTERFACE;


/*
** Timeout AST routine
*/
void timer_ast(pcap_t *p)
{
    p->timedout = 1;
}


/*
** Interface to device conversion routine. Converts a TCP/IP interface
** to a ASCIC VMS device for use by VCI.
**
** Example:
**
** WE0 becomes EWA
** SE1 becomes ESB
** XE0 becomes EXA
**
** For now we do not worry about pseudo interfaces, e.g WEA0
*/
int convert_interface_device(char *inter_name, INTERFACE *inter ) {
    char num_conv[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int status;
    char tmpdev[5];
    int tmpval;

    tmpdev[0] = 3;
    tmpdev[1] = toupper(inter_name[1]);
    tmpdev[2] = toupper(inter_name[0]);
    tmpval = (int) (char) inter_name[2] - (char) '0';
    if (tmpval < 0 || tmpval > sizeof(num_conv)) {
	return -1;
    }
    tmpdev[3] = num_conv[tmpval];
    tmpdev[4] = '\0';
    strcpy(inter->interface, inter_name);
    memcpy(inter->device, tmpdev, 5);
    return 0;
}
    
static char nosup[] = "live packet capture not supported on this system";


//
// This is kept in the execlet, so go get it.
//
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
{
    int status;
    PCAPSTAT vci_stat;

    status = pcapvci_get_statistics(p->vcmctx, &vci_stat);

    if $FAIL(status) {
	return (-1);
    }

    ps->ps_recv = vci_stat.recv_packets;
    ps->ps_drop = vci_stat.recv_packets_dropped;

    return 0;
}


/*
** Read a packet
*/
int pcap_read(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
    int status;
    struct pcap_pkthdr pcap_header;
    promisc_header promhdr;
    int timeout[2];
    int msec_mul = -10000;
    int once = 1;
    int packlen;
     
    //
    // If we're to timeout, set up timeout.
    //
    if (p->check_timeout && p->timeout > 0) {
	p->timedout = 0;
	status = lib$emul(&p->timeout, &msec_mul, &0, timeout);
	if $FAIL(status) {
	    return -1;
	}
	status = sys$setimr(0, timeout, &timer_ast, p, 0);
	if $FAIL(status) {
	    return -1;
	}
    }

    while (once || (p->check_timeout && !p->timedout)) {
	if (p->timeout != -1) {
	    once = 0;
	}
	// Read the packet
	packlen = pcapvci_read_packet(p->vcmctx, 1500, (char *)&p->lan_pkt);	

	if (packlen < 0) {
	    p->check_timeout = 0;
	    return -1;
	}

	if (packlen == 0) {
	    p->check_timeout = 0;
	    return 0;
	}
	
	if (p->fcode.bf_insns == NULL ||
	    bpf_filter(p->fcode.bf_insns, (unsigned char *)&p->lan_pkt, 
		packlen, packlen)) {

	    ++p->md.stat.ps_recv;
	    pcap_header.len = packlen;
	    pcap_header.caplen = packlen;
	    gettimeofday(&pcap_header.ts, NULL);
	    (*callback)(user, &pcap_header, (unsigned char *)&p->lan_pkt);
	    p->check_timeout = 0;
	    return 1;
	}
    }
    
    p->check_timeout = 0;
    return 0;
}


/*
** Send a packet. 
** One problem, the send it asynchronous, so we can't check for status.
*/
int pcap_sendpacket(pcap_t *p, u_char *buf, int size)
{
    int status;

    status = pcapvci_send_packet(p->vcmctx, 14, size, (char *)buf);

    return 0;
}


/*
** Open a connection. This is a bit tricky. We could use the LAN driver to listen
** to packets. However, we'd also like to be able to modify packets and send
** them off to the wire. In many cases when sending a packet we want to modify
** the sources address. While it is possible to modify the physical address
** using the LAN driver it affects the controller, not the device we created
** from the LAN driver template device. For this reason we have to use a
** VMS execlet that uses the undocumented VCI interface. The code for that
** part is a separate image, which we interface to via a buffer in the
** non paged pool, real cool.
*/
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
{
    int status;
    pcap_t *pcap_handle;
    char *ctlptr;
    char proto[5] = {8,0,0x2b,0x80,0x00};
    char pty[2] = {0x08,0x00};
    unsigned long ctldesc[2];
    INTERFACE interface;

    // Load the PCAP VCM execlet, if not already loaded
    status = pcapvci_load_execlet();
    if $FAIL(status) {
	return NULL;
    }

    // Convert interface to device
    status = convert_interface_device(device, &interface);
    if (status < 0) {
	return NULL;
    }

    pcap_handle = malloc(sizeof(pcap_t));
    if (!pcap_handle) {
	return NULL;
    }
    memset(pcap_handle, 0, sizeof(pcap_t));

    // Allocate a VCI port
    status = pcapvci_alloc_port(&pcap_handle->vcmctx);
    if $FAIL(status) {
	free(pcap_handle);
	return NULL;
    }

    // Create a VCI port
    status = pcapvci_create_port(pcap_handle->vcmctx, interface.device);
    if $FAIL(status) {
	free(pcap_handle);
	return NULL;
    }

    pcap_handle->lan_ctl = malloc(LANSIZE);
    if (!pcap_handle->lan_ctl) {
	free(pcap_handle);
	return NULL;
    }

    pcap_handle->bufsize = 64*1024;
    pcap_handle->buffer = malloc(pcap_handle->bufsize);

    //
    // Save timeout value
    //
    pcap_handle->timeout = to_ms;
    pcap_handle->check_timeout = 0;

    //
    // Link type ethernet
    //
    pcap_handle->linktype = DLT_EN10MB;

    //
    // Save snapshot length
    //
    pcap_handle->snapshot = snaplen;

    //
    // Use standard ethernet package type
    //
    ctlptr = pcap_handle->lan_ctl;
//    ADD_INT_VAL(ctlptr, NMA$C_PCLI_FMT, NMA$C_LINFM_802E);
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_FMT, NMA$C_LINFM_ETH);
//    ADD_INT_VAL(ctlptr, NMA$C_PCLI_SAP, 0xfffe);
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_PAD, NMA$C_STATE_OFF);
//    ADD_INT_VAL(ctlptr, NMA$C_PCLI_CRC, NMA$C_STATE_OFF);
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_MLT, NMA$C_STATE_ON);
//    ADD_INT_VAL(ctlptr, NMA$C_PCLI_ACC, NMA$C_ACC_SHR);
     
    
    //
    // Have device buffer 255 packets
    //
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_BFN, 255);
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_BUS, 2048);
//    ADD_INT_VAL(ctlptr, NMA$C_PCLI_DCH, NMA$C_STATE_ON);

    //
    // If promiscious mode, enable it
    //
    if (promisc) {
	ADD_INT_VAL(ctlptr, NMA$C_PCLI_PRM, NMA$C_STATE_ON);
    }

    //
    // All ethernet packets
    //
    ADD_INT_VAL(ctlptr, NMA$C_PCLI_PTY, *(int *)pty);
//    ADD_CNT_VAL(ctlptr, NMA$C_PCLI_PID, sizeof(proto), proto);

    //
    // Calculate length
    //
    ctldesc[0] = ctlptr - pcap_handle->lan_ctl;
    ctldesc[1] = (unsigned long)pcap_handle->lan_ctl;

    //
    // Enable the VCI port
    //
    status = pcapvci_enable_port(pcap_handle->vcmctx, ctldesc[0], (char *)ctldesc[1]);
    if $FAIL(status) {
	free(pcap_handle->lan_ctl);
	free(pcap_handle);
	return NULL;
    }

    return pcap_handle;
}


void pcap_close_vms(pcap_t *p)
{
    int status;

    //
    // Disable the port
    //
    status = pcapvci_disable_port(p->vcmctx);

    //
    // Delete the port
    //
    status = pcapvci_delete_port(p->vcmctx);

    //
    // Get rid of VCM context
    //
    status = pcapvci_free_port(p->vcmctx);

    //
    // Free up memory
    //
    if (p->lan_ctl) {
	free(p->lan_ctl);
    }
}


int pcap_platform_finddevs(pcap_if_t **alldevsp, char *errbuf)
{
	return (0);
}


int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
{
    if (install_bpf_program(p, fp) < 0)
	    return (-1);
    return (0);
}
