/* kx10_imp.c: IMP, interface message processor.

   Copyright (c) 2018-2020, Richard Cornwell based on code provided by
         Lars Brinkhoff and Danny Gasparovski.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICHARD CORNWELL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   This emulates the MIT-AI/ML/MC Host/IMP interface.
*/


#include "kx10_defs.h"
#include "sim_ether.h"

#if NUM_DEVS_IMP > 0
#define IMP_DEVNUM      0460
#define BBN_IMP_DEVNUM  0550
#define WA_IMP_DEVNUM   0400

#define DEVNUM imp_dib.dev_num

#define UNIT_V_DHCP     (UNIT_V_UF + 0)                 /* DHCP enable flag */
#define UNIT_DHCP       (1 << UNIT_V_DHCP)
#define UNIT_V_DTYPE    (UNIT_V_UF + 1)                 /* Type of IMP interface */
#define UNIT_M_DTYPE    3
#define UNIT_DTYPE      (UNIT_M_DTYPE << UNIT_V_DTYPE)
#define GET_DTYPE(x)    (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE)

#define TYPE_MIT        0              /* MIT Style KAIMP ITS */
#define TYPE_BBN        1              /* BBN style interface TENEX */
#define TYPE_WAITS      2              /* IMP connected to waits system. */

#define TYPE_UNI        0              /* Unibus byte order */
#define TYPE_SIMP       1              /* PDP10 string byte order */

#if KS
/* IMP11 interface */

/* CSR values */
#define CSR_GO    0000001  /* Go transfer */
#define CSR_RST   0000002  /* Reset interface */
#define CSR_UBA   0000060  /* Unibus upper address */
#define CSR_IE    0000100  /* Interrupt enable */
#define CSR_RDY   0000200  /* Device ready */
#define CSR_MRE   0001000  /* Master error */
#define CSR_NXM   0040000  /* Non existant memory */
#define CSR_ERR   0100000  /* Error present */

/* Input CSR  0767600 */
#define CSR_HRC   0000004  /* Host Ready Relay Control */
#define CSR_SE    0000010  /* Store enable */
#define CSR_IBF   0000400  /* Input Buffer full */
#define CSR_INR   0002000  /* IMP not ready */
#define CSR_HR    0004000  /* Host Ready */
#define CSR_EOM   0020000  /* End of Message */

/* Input data buffer   0767602 */
/* Input Bus Address   0767604 */
/* Input Word Count    0767606 */

/* Output CSR  07676010 */
#define CSR_ELB   0000004  /* Send EOM indication to IMP */
#define CSR_BB    0000010  /* Bus Back */
#define CSR_OBE   0000400  /* OUtput Buffer Empty */
#define CSR_WC0   0020000  /* Output Word Count 0 */

/* Output data buffer  0767612 */
/* Output Bus Address  0767614 */
/* Output Word Count   0767616 */

/* Bits in STATUS */
#define IMPID       010 /* Input done. */
#define IMPIB       040 /* Input busy. */
#define IMPOD      0100 /* Output done. */
#define IMPOB      0400 /* Output busy. */
#define IMPERR    01000 /* IMP error. */
#define IMPR      02000 /* IMP ready. */
#define IMPIC     04000 /* IMP interrupt condition. */
#define IMPHER   010000 /* Host error. */
#define IMPHR    020000 /* Host ready. */
#define IMPIHE   040000 /* Inhibit interrupt on host error. */
#define IMPLW   0100000 /* Last IMP word. */
#else


/* ITS IMP Bits */

/* CONI */
#define IMPID       010 /* Input done. */
#define IMPI32      020 /* Input in 32 bit mode. */
#define IMPIB       040 /* Input busy. */
#define IMPOD      0100 /* Output done. */
#define IMPO32     0200 /* Output in 32-bit mode. */
#define IMPOB      0400 /* Output busy. */
#define IMPERR    01000 /* IMP error. */
#define IMPR      02000 /* IMP ready. */
#define IMPIC     04000 /* IMP interrupt condition. */
#define IMPHER   010000 /* Host error. */
#define IMPHR    020000 /* Host ready. */
#define IMPIHE   040000 /* Inhibit interrupt on host error. */
#define IMPLW   0100000 /* Last IMP word. */

/* CONO */
#define IMPIDC      010 /* Clear input done */
#define IMI32S      020 /* Set 32-bit output */
#define IMI32C      040 /* Clear 32-bit output */
#define IMPODC     0100 /* Clear output done */
#define IMO32S     0200 /* Set 32-bit input */
#define IMO32C     0400 /* Clear 32-bit input */
#define IMPODS    01000 /* Set output done */
#define IMPIR     04000 /* Enable interrupt on IMP ready */
#define IMPHEC   010000 /* Clear host error */
#define IMIIHE   040000 /* Inhibit interrupt on host error */
#define IMPLHW  0200000 /* Set last host word. */

/* BBN IMP BITS */

/* CONO bits */
#define IMP_EN_IN   00000010  /* Enable input PIA channel */
#define IMP_EN_OUT  00000200  /* Enable output PIA channel */
#define IMP_EN_END  00004000  /* Enable end PIA channel */
#define IMP_END_IN  00010000  /* End of input */
#define IMP_END_OUT 00020000  /* End of output */
#define IMP_STOP    00040000  /* Stop the imp */
#define IMP_PDP_DN  00100000  /* PDP-10 is down */
#define IMP_CLR     00200000  /* Clear imp down flag */
#define IMP_RST     00400000  /* Reset IMP */

/* CONI bits */
#define IMP_IFULL   00000010  /* Input full */
#define IMP_OEMPY   00000200  /* Output empty */
#define IMP_ENDIN   00014000  /* End of input */
#define IMP_DN      00020000  /* IMP down */
#define IMP_WAS_DN  00040000  /* IMP was down */
#define IMP_PWR     00200000  /* IMP Rdy */

/* WAITS IMP BITS */

/* CONO bits  */
#define IMP_ODPIEN   0000010  /* Enable change of output done PIA, also set byte size */
#define IMP_IDPIEN   0000020  /* Enable change of input done PIA, also set byte size */
#define IMP_IEPIEN   0000040  /* Change end of input PIA */
#define IMP_FINO     0000100  /* Last bit of output */
#define IMP_STROUT   0000200  /* Start output */
#define IMP_CLRWT    0002000  /* Clear waiting to input bit */
#define IMP_CLRST    0004000  /* Clear stop after input bit */
#define IMP_O32      0010000  /* Set output to 32bit */
#define IMP_I32      0020000  /* Set input to 32bit */
#define IMP_STRIN    0040000  /* Start input */
#define IMP_TEST     0100000  /* Test mode */

/* CONI bits */
#define IMP_ODONE    0004000  /* Output done */
#define IMP_IEND     0010000  /* Input end. */
#define IMP_IDONE    0020000  /* Input done */
#define IMP_ERR      0040000  /* Imp error */
#define IMP_RDY      0200000  /* Imp ready */
#define IMP_OCHN     0000007
#define IMP_ICHN     0000070
#define IMP_ECHN     0000700

/* CONI timeout.  If no CONI instruction is executed for 3-5 seconds,
   the interface will raise the host error signal. */
#define CONI_TIMEOUT 3000000
#endif

#define STATUS     u3
#define OPOS       u4    /* Output bit position */
#define IPOS       u5    /* Input bit position */
#define ILEN       u6    /* Size of input buffer in bits */


#define IMP_ARPTAB_SIZE        64
#define IMP_ARP_MAX_AGE        100

uint32 mask[] = {
     0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8,
     0xFFFFFFF0, 0xFFFFFFE0, 0xFFFFFFC0, 0xFFFFFF80,
     0xFFFFFF00, 0xFFFFFE00, 0xFFFFFC00, 0xFFFFF800,
     0xFFFFF000, 0xFFFFE000, 0xFFFFC000, 0xFFFF8000,
     0xFFFF0000, 0xFFFE0000, 0xFFFC0000, 0xFFF80000,
     0xFFF00000, 0xFFE00000, 0xFFC00000, 0xFF800000,
     0xFF000000, 0xFE000000, 0xFC000000, 0xF8000000,
     0xF0000000, 0xE0000000, 0xC0000000, 0x80000000,
     0x00000000};

typedef uint32 in_addr_T;

PACKED_BEGIN
struct imp_eth_hdr {
    ETH_MAC    dest;
    ETH_MAC    src;
    uint16     type;
} PACKED_END;

#define ETHTYPE_ARP 0x0806
#define ETHTYPE_IP  0x0800

/*
 * Structure of an internet header, naked of options.
 */
PACKED_BEGIN
struct ip {
    uint8           ip_v_hl;            /* version,header length */
    uint8           ip_tos;             /* type of service */
    uint16          ip_len;             /* total length */
    uint16          ip_id;              /* identification */
    uint16          ip_off;             /* fragment offset field */
#define IP_DF 0x4000                    /* don't fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
    uint8           ip_ttl;             /* time to live */
    uint8           ip_p;               /* protocol */
    uint16          ip_sum;             /* checksum */
    in_addr_T       ip_src;
    in_addr_T       ip_dst;             /* source and dest address */
} PACKED_END;

#define TCP_PROTO  6
PACKED_BEGIN
struct tcp {
    uint16          tcp_sport;          /* Source port */
    uint16          tcp_dport;          /* Destination port */
    uint32          seq;                /* Sequence number */
    uint32          ack;                /* Ack number */
    uint16          flags;              /* Flags */
#define TCP_FL_FIN  0x01
#define TCP_FL_SYN  0x02
#define TCP_FL_RST  0x04
#define TCP_FL_PSH  0x08
#define TCP_FL_ACK  0x10
#define TCP_FL_URG  0x20
    uint16          window;             /* Window size */
    uint16          chksum;             /* packet checksum */
    uint16          urgent;             /* Urgent pointer */
} PACKED_END;

#define UDP_PROTO 17
PACKED_BEGIN
struct udp {
    uint16          udp_sport;          /* Source port */
    uint16          udp_dport;          /* Destination port */
    uint16          len;                /* Length */
    uint16          chksum;             /* packet checksum */
} PACKED_END;

PACKED_BEGIN
struct udp_hdr {
    in_addr_T       ip_src;
    in_addr_T       ip_dst;             /* source and dest address */
    uint8           zero;
    uint8           proto;              /* Protocol */
    uint16          hlen;               /* Length of header and data */
} PACKED_END;

#define ICMP_PROTO 1
PACKED_BEGIN
struct icmp {
    uint8           type;               /* Type of packet */
    uint8           code;               /* Code */
    uint16          chksum;             /* packet checksum */
} PACKED_END;

PACKED_BEGIN
struct ip_hdr {
    struct imp_eth_hdr  ethhdr;
    struct ip           iphdr;
} PACKED_END;

#define ARP_REQUEST     1
#define ARP_REPLY       2
#define ARP_HWTYPE_ETH  1

PACKED_BEGIN
struct arp_hdr {
    struct imp_eth_hdr  ethhdr;
    uint16              hwtype;
    uint16              protocol;
    uint8               hwlen;
    uint8               protolen;
    uint16              opcode;
    ETH_MAC             shwaddr;
    in_addr_T           sipaddr;
    ETH_MAC             dhwaddr;
    in_addr_T           dipaddr;
    uint8               padding[18];
} PACKED_END;

struct arp_entry {
    in_addr_T  ipaddr;
    ETH_MAC    ethaddr;
    int16      age;
#define ARP_DONT_AGE -1
};

/* DHCP client states */
#define DHCP_STATE_OFF              0
#define DHCP_STATE_REQUESTING       1
#define DHCP_STATE_INIT             2
#define DHCP_STATE_REBOOTING        3
#define DHCP_STATE_REBINDING        4
#define DHCP_STATE_RENEWING         5
#define DHCP_STATE_SELECTING        6
#define DHCP_STATE_INFORMING        7
#define DHCP_STATE_CHECKING         8
#define DHCP_STATE_PERMANENT        9   /* not yet implemented */
#define DHCP_STATE_BOUND            10
#define DHCP_STATE_RELEASING        11  /* not yet implemented */
#define DHCP_STATE_BACKING_OFF      12

/* DHCP op codes */
#define DHCP_BOOTREQUEST            1
#define DHCP_BOOTREPLY              2

/* DHCP message types */
#define DHCP_DISCOVER               1
#define DHCP_OFFER                  2
#define DHCP_REQUEST                3
#define DHCP_DECLINE                4
#define DHCP_ACK                    5
#define DHCP_NAK                    6
#define DHCP_RELEASE                7
#define DHCP_INFORM                 8

/** DHCP hardware type, currently only ethernet is supported */
#define DHCP_HTYPE_ETH              1

#define DHCP_MAGIC_COOKIE           0x63825363UL

#define DHCP_INFINITE_LEASE         0xFFFFFFFF

#define DHCP_UDP_PORT_CLIENT        68
#define DHCP_UDP_PORT_SERVER        67

/* This is a list of options for BOOTP and DHCP, see RFC 2132 for descriptions */

/* BootP options */
#define DHCP_OPTION_PAD             0
#define DHCP_OPTION_SUBNET_MASK     1 /* RFC 2132 3.3 */
#define DHCP_OPTION_ROUTER          3
#define DHCP_OPTION_DNS_SERVER      6
#define DHCP_OPTION_HOSTNAME        12
#define DHCP_OPTION_IP_TTL          23
#define DHCP_OPTION_MTU             26
#define DHCP_OPTION_BROADCAST       28
#define DHCP_OPTION_TCP_TTL         37
#define DHCP_OPTION_NTP             42
#define DHCP_OPTION_END             255

/* DHCP options */
#define DHCP_OPTION_REQUESTED_IP    50 /* RFC 2132 9.1, requested IP address */
#define DHCP_OPTION_LEASE_TIME      51 /* RFC 2132 9.2, time in seconds, in 4 bytes */
#define DHCP_OPTION_OVERLOAD        52 /* RFC 2132 9.3, use file and/or sname field for options */

#define DHCP_OPTION_MESSAGE_TYPE    53 /* RFC 2132 9.6, important for DHCP */
#define DHCP_OPTION_MESSAGE_TYPE_LEN 1

#define DHCP_OPTION_SERVER_ID       54 /* RFC 2132 9.7, server IP address */
#define DHCP_OPTION_PARAMETER_REQUEST_LIST  55 /* RFC 2132 9.8, requested option types */

#define DHCP_OPTION_MAX_MSG_SIZE    57 /* RFC 2132 9.10, message size accepted >= 576 */
#define DHCP_OPTION_MAX_MSG_SIZE_LEN 2

#define DHCP_OPTION_T1              58 /* T1 renewal time */
#define DHCP_OPTION_T2              59 /* T2 rebinding time */
#define DHCP_OPTION_US              60
#define DHCP_OPTION_CLIENT_ID       61
#define DHCP_OPTION_TFTP_SERVERNAME 66
#define DHCP_OPTION_BOOTFILE        67

/* possible combinations of overloading the file and sname fields with options */
#define DHCP_OVERLOAD_NONE          0
#define DHCP_OVERLOAD_FILE          1
#define DHCP_OVERLOAD_SNAME         2
#define DHCP_OVERLOAD_SNAME_FILE    3

#define DHCP_CHADDR_LEN             16
#define DHCP_SNAME_LEN              64
#define DHCP_FILE_LEN               128

PACKED_BEGIN
struct dhcp {
    uint8             op;                      /* Operation */
    uint8             htype;                   /* Header type */
    uint8             hlen;                    /* Ether Header len */
    uint8             hops;                    /*  ops? */
    uint32            xid;                     /* id number */
    uint16            secs;
    uint16            flags;
    in_addr_T         ciaddr;                  /* Client IP address */
    in_addr_T         yiaddr;                  /* Your IP address */
    in_addr_T         siaddr;                  /* Server IP address */
    in_addr_T         giaddr;                  /* Gateway IP address */
    uint8             chaddr[DHCP_CHADDR_LEN];
    uint8             sname[DHCP_SNAME_LEN];
    uint8             file[DHCP_FILE_LEN];
    uint32            cookie;                  /* magic cookie */
    uint8             options[100];            /* Space for options */
} PACKED_END;

struct imp_packet {
    struct imp_packet *next;                   /* Link to packets */
    ETH_PACK          packet;
    in_addr_T         dest;                    /* Destination IP address */
    uint16            msg_id;                  /* Message ID */
    int               life;                    /* How many ticks to wait */
} imp_buffer[8];

struct imp_map {
    uint16            sport;                   /* Port to fix */
    uint16            dport;                   /* Port to fix */
    uint16            cls_tim;                 /* Close timer */
    uint32            adj;                     /* Amount to adjust */
    uint32            lseq;                    /* Sequence number last adjusted */
};


struct imp_stats {
    int               recv;                    /* received packets */
    int               dropped;                 /* received packets dropped */
    int               xmit;                    /* transmitted packets */
    int               fail;                    /* transmit failed */
    int               runt;                    /* runts */
    int               reset;                   /* reset count */
    int               giant;                   /* oversize packets */
    int               setup;                   /* setup packets */
    int               loop;                    /* loopback packets */
    int               recv_overrun;            /* receiver overruns */
};


struct imp_device {
    ETH_PCALLBACK     rcallback;               /* read callback routine */
    ETH_PCALLBACK     wcallback;               /* write callback routine */
    ETH_MAC           macs[2];                 /* Hardware MAC addresses */
#define mac macs[0]
#define bcast macs[1]
    struct imp_packet *sendq;                  /* Send queue */
    struct imp_packet *freeq;                  /* Free queue */
    in_addr_T         ip;                      /* Local IP address */
    in_addr_T         ip_mask;                 /* Local IP mask */
    in_addr_T         hostip;                  /* IP address of local host */
    in_addr_T         gwip;                    /* Gateway IP address */
    int               maskbits;                /* Mask length */
    struct imp_map    port_map[64];            /* Ports to adjust */
    in_addr_T         dhcpip;                  /* DHCP server address */
    uint8             dhcp_state;              /* State of DHCP */
    uint32            dhcp_lease;              /* DHCP lease time */
    int               dhcp_renew;              /* DHCP renew time */
    int               dhcp_rebind;             /* DHCP rebind time */
    int               dhcp_wait_time;          /* seconds waiting for response */
    int               dhcp_retry_after;        /* timeout time */
    int               init_state;              /* Initialization state */
    uint32            dhcp_xid;                /* Transaction ID */
    int               padding;                 /* Type zero padding */
    uint64            obuf;                    /* Output buffer */
    uint64            ibuf;                    /* Input buffer */
    int               obits;                   /* Output bits */
    int               ibits;                   /* Input bits */
    struct imp_stats  stats;
    uint8             sbuffer[ETH_FRAME_SIZE]; /* Temp send buffer */
    uint8             rbuffer[ETH_FRAME_SIZE]; /* Temp receive buffer */
    ETH_DEV           etherface;
    ETH_QUE           ReadQ;
    int               imp_error;
    int               host_error;
    int               rfnm_count;              /* Number of pending RFNM packets */
    int               pia;                     /* PIA channels */
    struct arp_entry  arp_table[IMP_ARPTAB_SIZE];
} imp_data;

extern int32 tmxr_poll;

static CONST ETH_MAC broadcast_ethaddr = {0xff,0xff,0xff,0xff,0xff,0xff};

static CONST in_addr_T broadcast_ipaddr = {0xffffffff};

#if KS
int            imp_wr(DEVICE *dptr, t_addr addr, uint16 data, int32 access);
int            imp_rd(DEVICE *dptr, t_addr addr, uint16 *data, int32 access);
uint16         imp_vect(struct pdp_dib *dibp);
#else
t_stat         imp_devio(uint32 dev, uint64 *data);
t_addr         imp_devirq(uint32 dev, t_addr addr);
#endif
t_stat         imp_srv(UNIT *);
t_stat         imp_eth_srv(UNIT *);
t_stat         imp_tim_srv(UNIT *);
t_stat         imp_reset (DEVICE *dptr);
t_stat         imp_set_mpx (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat         imp_show_mpx (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_show_mac (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat         imp_set_mac (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat         imp_show_ip (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_set_ip (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat         imp_show_gwip (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_set_gwip (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat         imp_show_hostip (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_set_hostip (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat         imp_show_dhcpip (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_show_arp (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat         imp_set_arp (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
void           imp_timer_task(struct imp_device *imp);
void           imp_send_rfmn(struct imp_device *imp);
void           imp_packet_in(struct imp_device *imp);
void           imp_send_packet (struct imp_device *imp_data, int len);
void           imp_free_packet(struct imp_device *imp, struct imp_packet *p);
struct imp_packet * imp_get_packet(struct imp_device *imp);
void           imp_arp_update(struct imp_device *imp, in_addr_T ipaddr, ETH_MAC ethaddr, int age);
void           imp_arp_arpin(struct imp_device *imp, ETH_PACK *packet);
void           imp_arp_arpout(struct imp_device *imp, in_addr_T ipaddr);
struct arp_entry * imp_arp_lookup(struct imp_device *imp, in_addr_T ipaddr);
void           imp_packet_out(struct imp_device *imp, ETH_PACK *packet);
void           imp_packet_debug(struct imp_device *imp, const char *action, ETH_PACK *packet);
void           imp_write(struct imp_device *imp, ETH_PACK *packet);
void           imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *packet);
void           imp_dhcp_timer(struct imp_device *imp);
void           imp_dhcp_discover(struct imp_device *imp);
void           imp_dhcp_request(struct imp_device *imp, in_addr_T dhcpip);
void           imp_dhcp_release(struct imp_device *imp);
void           imp_arp_age(struct imp_device *imp);
t_stat         imp_attach (UNIT * uptr, CONST char * cptr);
t_stat         imp_detach (UNIT * uptr);
t_stat         imp_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
const char     *imp_description (DEVICE *dptr);
static char    *ipv4_inet_ntoa(struct in_addr ip);
static int     ipv4_inet_aton(const char *str, struct in_addr *inp);

#if KS
uint16        imp_icsr;
uint16        imp_idb;
uint32        imp_iba;
uint16        imp_iwcnt;
uint16        imp_ocsr;
uint16        imp_odb;
uint32        imp_oba;
uint16        imp_owcnt;
#endif


int       imp_mpx_lvl = 0;
double    last_coni;

UNIT imp_unit[] = {
    {UDATA(imp_srv,     UNIT_IDLE+UNIT_ATTABLE+UNIT_DHCP, 0)},  /* 0 */
    {UDATA(imp_eth_srv, UNIT_IDLE+UNIT_DIS,     0)},  /* 1 */
    {UDATA(imp_tim_srv, UNIT_IDLE+UNIT_DIS,     0)},  /* 2 */
};

#if KS
DIB imp_dib = {0767600, 017, 0250, 6, 3, &imp_rd, &imp_wr, 0, 0, 0};
#else
DIB imp_dib = {IMP_DEVNUM, 1, &imp_devio,
#if KL
        &imp_devirq,
#else
        NULL
#endif
};
#endif

MTAB imp_mod[] = {
    { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx",
      &imp_set_mac, &imp_show_mac, NULL, "MAC address" },
#if MPX_DEV
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MPX", "MPX",
      &imp_set_mpx, &imp_show_mpx, NULL, "ITS Interrupt Channel #"},
#endif
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "IP", "IP=ddd.ddd.ddd.ddd/dd",
      &imp_set_ip, &imp_show_ip, NULL, "IP address" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "GW", "GW=ddd.ddd.ddd.ddd",
      &imp_set_gwip, &imp_show_gwip, NULL, "Gateway address" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "HOST", "HOST=ddd.ddd.ddd.ddd",
      &imp_set_hostip, &imp_show_hostip, NULL, "HOST IP address" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "ETH", NULL, NULL,
      &eth_show, NULL, "Display attachedable devices" },
    { UNIT_DHCP, 0, NULL, "NODHCP", NULL, NULL, NULL,
           "Don't aquire address from DHCP"},
    { UNIT_DHCP, UNIT_DHCP, "DHCP", "DHCP", NULL, NULL, NULL,
           "Use DHCP to set IP address"},
    { MTAB_XTD|MTAB_VDV, 0, "DHCPIP", NULL,
      NULL, &imp_show_dhcpip, NULL, "DHCP info" },
#if KS
    { UNIT_DTYPE, (TYPE_UNI << UNIT_V_DTYPE), "UNI", "UNI", NULL, NULL,  NULL,
           "Standard Unibus transfers"},
    { UNIT_DTYPE, (TYPE_SIMP << UNIT_V_DTYPE), "SIMP", "SIMP", NULL, NULL,  NULL,
           "PDP10 byte order transfers"},
#else
    { UNIT_DTYPE, (TYPE_MIT << UNIT_V_DTYPE), "MIT", "MIT", NULL, NULL,  NULL,
           "ITS/MIT style interface"},
    { UNIT_DTYPE, (TYPE_BBN << UNIT_V_DTYPE), "BBN", "BBN", NULL, NULL,  NULL,
           "Tenex/BBN style interface"},
    { UNIT_DTYPE, (TYPE_WAITS << UNIT_V_DTYPE), "WAITS", "WAITS", NULL, NULL,  NULL,
           "WAITS style interface"},
#endif
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "ARP", NULL,
      NULL, &imp_show_arp, NULL, "ARP IP address->MAC address table" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "ARP=ddd.ddd.ddd.ddd=XX:XX:XX:XX:XX:XX",
      &imp_set_arp, NULL, NULL, "Create a static ARP Entry" },
#if KS
    {MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "ADDR", "ADDR",  &uba_set_addr, uba_show_addr,
            NULL, "Sets address of IMP11" },
    {MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECT", "VECT",  &uba_set_vect, uba_show_vect,
            NULL, "Sets vect of IMP11" },
    {MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BR", "BR",  &uba_set_br, uba_show_br,
            NULL, "Sets br of IMP11" },
    {MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CTL", "CTL",  &uba_set_ctl, uba_show_ctl,
            NULL, "Sets uba of IMP11" },
#endif
    { 0 }
    };

/* Simulator debug controls */
DEBTAB              imp_debug[] = {
    {"CMD", DEBUG_CMD, "Show command execution to devices"},
    {"DATA", DEBUG_DATA, "Show data transfers"},
    {"DETAIL", DEBUG_DETAIL, "Show details about device"},
    {"EXP", DEBUG_EXP, "Show exception information"},
    {"CONI", DEBUG_CONI, "Show coni instructions"},
    {"CONO", DEBUG_CONO, "Show cono instructions"},
    {"DATAIO", DEBUG_DATAIO, "Show datai and datao instructions"},
    {"IRQ", DEBUG_IRQ, "Show IRQ requests"},
#define DEBUG_DHCP (DEBUG_IRQ<<1)
    {"DHCP", DEBUG_DHCP, "Show DHCP activities"},
#define DEBUG_ARP (DEBUG_DHCP<<1)
    {"ARP", DEBUG_ARP, "Show ARP activities"},
#define DEBUG_TCP (DEBUG_ARP<<1)
    {"TCP", DEBUG_TCP, "Show TCP packet activities"},
#define DEBUG_UDP (DEBUG_TCP<<1)
    {"UDP", DEBUG_UDP, "Show UDP packet activities"},
#define DEBUG_ICMP (DEBUG_UDP<<1)
    {"ICMP", DEBUG_ICMP, "Show ICMP packet activities"},
#define DEBUG_ETHER (DEBUG_ICMP<<1)
    {"ETHER", DEBUG_ETHER, "Show ETHER activities"},
    {0, 0}
};

REG                 imp_reg[] = {
    {SAVEDATA(DATA, imp_data) },
    {0}
};

DEVICE imp_dev = {
    "IMP", imp_unit, imp_reg, imp_mod,
    3, 8, 0, 1, 8, 36,
    NULL, NULL, &imp_reset, NULL, &imp_attach, &imp_detach,
    &imp_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_ETHER, 0, imp_debug,
    NULL, NULL, &imp_help, NULL, NULL, &imp_description
};
#define IMP_OCHN     0000007
#define IMP_ICHN     0000070
#define IMP_ECHN     0000700

#if KS
static void check_interrupts (UNIT *uptr)
{
    DEVICE *dptr = &imp_dev;
    struct pdp_dib   *dibp = (DIB *)dptr->ctxt;

    if ((uptr->STATUS & IMPID) != 0 && imp_icsr & CSR_IE) {
       uba_set_irq(dibp, dibp->uba_vect);
    }
}

int
imp_wr(DEVICE *dptr, t_addr addr, uint16 data, int32 access)
{
    struct pdp_dib   *dibp = (DIB *)dptr->ctxt;
    UNIT             *uptr = imp_unit;

    addr &= dibp->uba_mask;
    sim_debug(DEBUG_DETAIL, dptr, "IMP11 write %06o %06o %o\n",
             addr, data, access);

    switch (addr & 016) {
    case 000:     /* Input CSR */
            if (access == BYTE) {
               if (addr & 1)
                   data = data | (imp_icsr & 0377);
               else
                   data = (imp_icsr & 0177400) | data;
            }
            if (data & CSR_RST) {
               imp_icsr = CSR_INR|CSR_RDY;
               imp_iba = 0;
               imp_iwcnt = 0;
               uba_clr_irq(dibp, dibp->uba_vect);
            }
            data &= (CSR_GO|CSR_RST|CSR_UBA|CSR_IE|CSR_HRC|CSR_SE);
            imp_icsr &= ~(CSR_GO|CSR_RST|CSR_UBA|CSR_IE|CSR_HRC|CSR_SE|CSR_ERR
                         |CSR_NXM|CSR_MRE);
            imp_icsr |= data;
            if (data & CSR_HRC) {
                imp_icsr |= CSR_HR;
            } else {
                imp_icsr &= ~CSR_HR;
                imp_icsr |= CSR_INR;
            }
            if (data & CSR_GO) {
                imp_icsr &= ~CSR_RDY;
                uptr->STATUS &= ~(IMPID|IMPLW);
                uba_clr_irq(dibp, dibp->uba_vect);
            }
            sim_debug(DEBUG_DETAIL, dptr, "IMP11 icsr %06o\n", imp_icsr);
            break;

    case 002:     /* Input Data Buffer */
            if (access == BYTE) {
               if (addr & 1)
                   data = data | (imp_idb & 0377);
               else
                   data = (imp_idb & 0177400) | data;
            }
            imp_idb = data;
            break;

    case 004:     /* Input Bus Address */
            imp_iba = (data & 0177777);
            imp_icsr &= ~(CSR_ERR);
            break;

    case 006:     /* Input Word Count */
            imp_iwcnt = (data & 0177777);
            break;

    case 010:     /* Output CSR */
            if (access == BYTE) {
               if (addr & 1)
                   data = data | (imp_ocsr & 0377);
               else
                   data = (imp_ocsr & 0177400) | data;
            }
            if (data & CSR_RST) {
               imp_ocsr |= CSR_RDY;
               imp_oba = 0;
               imp_owcnt = 0;
               uba_clr_irq(dibp, dibp->uba_vect + 4);
            }
            data &= (CSR_GO|CSR_RST|CSR_UBA|CSR_IE|CSR_BB|CSR_ELB);
            imp_ocsr &= ~(CSR_GO|CSR_RST|CSR_UBA|CSR_IE|CSR_BB|CSR_ELB|CSR_ERR
                         |CSR_NXM|CSR_MRE);
            imp_ocsr |= data;
            if (data & CSR_GO) {
                imp_ocsr &= ~CSR_RDY;
                uptr->STATUS &= ~(IMPOD);
                uba_clr_irq(dibp, dibp->uba_vect + 4);
            }
            sim_debug(DEBUG_DETAIL, dptr, "IMP11 ocsr %06o\n", imp_ocsr);
            break;

    case 012:     /* Output Data Buffer */
            if (access == BYTE) {
               if (addr & 1)
                   data = data | (imp_odb & 0377);
               else
                   data = (imp_odb & 0177400) | data;
            }
            imp_odb = data;
            break;

    case 014:     /* Output Bus Address */
            imp_oba = (data & 0177777);
            imp_ocsr &= ~(CSR_ERR);
            break;

    case 016:     /* Output Word Count */
            imp_owcnt = (data & 0177777);
            break;
    }
    if (imp_ocsr & CSR_GO || imp_icsr & CSR_GO)
        sim_activate(uptr, 100);
    return 0;
}

int
imp_rd(DEVICE *dptr, t_addr addr, uint16 *data, int32 access)
{
    struct pdp_dib   *dibp = (DIB *)dptr->ctxt;
    UNIT             *uptr = imp_unit;

    addr &= dibp->uba_mask;

    switch (addr & 016) {
    case 000:     /* Input CSR */
            *data = imp_icsr;
            if ((uptr->STATUS & (IMPID)) != 0)
                *data |= CSR_IBF;
            if ((uptr->STATUS & (IMPLW)) != 0)
                *data |= CSR_EOM;
            break;
    case 002:     /* Input Data Buffer */
            *data = imp_idb;
            break;

    case 004:     /* Input Bus Address */
            *data = imp_iba;
            break;

    case 006:     /* Input Word Count */
            *data = imp_iwcnt;
            break;

    case 010:     /* Output CSR */
            *data = imp_ocsr;
            if ((uptr->STATUS & (IMPOD)) != 0)
                *data |= CSR_OBE;
            break;

    case 012:     /* Output Data Buffer */
            *data = imp_odb;
            break;

    case 014:     /* Output Bus Address */
            *data = imp_oba;
            break;

    case 016:     /* Output Word Count */
            *data = imp_owcnt;
            break;
    }
    sim_debug(DEBUG_DETAIL, dptr, "IMP11 read %06o %06o %o\n",
             addr, *data, access);
    return 0;

}

#else
static void check_interrupts (UNIT *uptr)
{
    clr_interrupt (DEVNUM);

    if ((uptr->STATUS & (IMPERR | IMPIC)) == IMPERR)
        set_interrupt(DEVNUM, imp_data.pia >> 6);
    if ((uptr->STATUS & (IMPR | IMPIC)) == (IMPR | IMPIC))
        set_interrupt(DEVNUM, imp_data.pia >> 6);
    if ((uptr->STATUS & (IMPHER | IMPIHE)) == IMPHER)
        set_interrupt(DEVNUM, imp_data.pia >> 6);
    if (uptr->STATUS & IMPID) {
        if (uptr->STATUS & IMPLW)
            set_interrupt(DEVNUM, imp_data.pia);
        else
            set_interrupt_mpx(DEVNUM, imp_data.pia, imp_mpx_lvl);
    }
    if (uptr->STATUS & IMPOD)
       set_interrupt_mpx(DEVNUM, imp_data.pia >> 3, imp_mpx_lvl + 1);
}

t_stat imp_devio(uint32 dev, uint64 *data)
{
    DEVICE *dptr = &imp_dev;
    UNIT   *uptr = imp_unit;

    switch(dev & 07) {
    case CONO:
        sim_debug(DEBUG_CONO, dptr, "IMP %03o CONO %06o PC=%o\n", dev,
                 (uint32)*data, PC);
        switch (GET_DTYPE(uptr->flags)) {
        case TYPE_MIT:
             imp_data.pia = *data & 7;
             imp_data.pia = (imp_data.pia << 6) | (imp_data.pia << 3) | imp_data.pia;
             if (*data & IMPIDC) /* Clear input done. */
                 uptr->STATUS &= ~IMPID;
             if (*data & IMI32S) /* Set 32-bit input. */
                 uptr->STATUS |= IMPI32;
             if (*data & IMI32C) /* Clear 32-bit input */
                 uptr->STATUS &= ~IMPI32;
             if (*data & IMPODC) /* Clear output done. */
                 uptr->STATUS &= ~IMPOD;
             if (*data & IMO32C) /* Clear 32-bit output. */
                 uptr->STATUS &= ~IMPO32;
             if (*data & IMO32S) /* Set 32-bit output. */
                 uptr->STATUS |= IMPO32;
             if (*data & IMPODS) /* Set output done. */
                 uptr->STATUS |= IMPOD;
             if (*data & IMPIR) { /* Enable interrupt on IMP ready. */
                 uptr->STATUS |= IMPIC;
                 uptr->STATUS &= ~IMPERR;
             }
             if (*data & IMPHEC) { /* Clear host error. */
                 /* Only if there has been a CONI lately. */
                 if (last_coni - sim_gtime() < CONI_TIMEOUT)
                     uptr->STATUS &= ~IMPHER;
             }
             if (*data & IMIIHE) /* Inhibit interrupt on host error. */
                 uptr->STATUS |= IMPIHE;
             if (*data & IMPLHW) /* Last host word. */
                 uptr->STATUS |= IMPLHW;
             check_interrupts(uptr);
             break;
        case TYPE_BBN:
        case TYPE_WAITS:
             if (*data & IMP_ODPIEN) {
                 imp_data.pia &= ~07;
                 imp_data.pia |= *data & 07;
                 uptr->STATUS &= ~(IMPO32|IMPLHW|IMPOD);
                 if (*data & IMP_O32)
                     uptr->STATUS |= IMPO32;
             }
             if (*data & IMP_IDPIEN) {
                 imp_data.pia &= ~070;
                 imp_data.pia |= (*data & 07) << 3;
                 uptr->STATUS &= ~(IMPI32|IMPID);
                 if (*data & IMP_I32)
                     uptr->STATUS |= IMPI32;
             }
             if (*data & IMP_IEPIEN) {
                 imp_data.pia &= ~0700;
                 imp_data.pia |= (*data & 07) << 6;
             }
             if (*data & IMP_FINO) {
                 if (uptr->STATUS & IMPOD) {
                     imp_send_packet (&imp_data, uptr->OPOS >> 3);
                     /* Allow room for ethernet header for later */
                     memset(imp_data.sbuffer, 0, ETH_FRAME_SIZE);
                     uptr->OPOS = 0;
                     uptr->STATUS &= ~(IMPLHW);
                 } else
                     uptr->STATUS |= IMPLHW;
             }
             if (*data & IMP_STROUT)
                 uptr->STATUS &= ~(IMPOD|IMPLHW);
             if (*data & IMP_CLRWT) { /* Not sure about this yet. */
                 uptr->STATUS &= ~IMPID;
             }
             if (*data & IMP_CLRST)   /* Not sure about this yet. */
                 uptr->STATUS &= ~IMPID;
             if (*data & IMP_STRIN) {
                 uptr->STATUS &= ~IMPID;
                 uptr->ILEN = 0;
             }
             check_interrupts(uptr);
             break;
        }
        break;
    case CONI:
        switch (GET_DTYPE(uptr->flags)) {
        case TYPE_MIT:
             last_coni = sim_gtime();
             *data = (uint64)(uptr->STATUS | (imp_data.pia & 07));
             break;
        case TYPE_BBN:
        case TYPE_WAITS:
             *data = (uint64)(imp_data.pia & 0777);
             if (uptr->STATUS & IMPOD)
                 *data |= IMP_ODONE;
             if (uptr->STATUS & IMPID)
                 *data |= IMP_IDONE;
             if (uptr->STATUS & IMPR)
                 *data |= IMP_RDY;
             if (uptr->STATUS & IMPLW)
                 *data |= IMP_IEND;
             if (uptr->STATUS & (IMPERR|IMPHER))
                 *data |= IMP_ERR;
             break;
        }
        sim_debug(DEBUG_CONI, dptr, "IMP %03o CONI %012llo PC=%o\n", dev,
                           *data, PC);
        break;
    case DATAO:
        uptr->STATUS |= IMPOB;
        uptr->STATUS &= ~IMPOD;
        imp_data.obuf = *data;
        imp_data.obits = (uptr->STATUS & IMPO32) ? 32 : 36;
        sim_debug(DEBUG_DATAIO, dptr, "IMP %03o DATO %012llo %d %08x PC=%o\n",
                 dev, *data, imp_data.obits, (uint32)(*data >> 4), PC);
        sim_activate(uptr, 100);
        break;
    case DATAI:
        *data = imp_data.ibuf;
        uptr->STATUS &= ~(IMPID|IMPLW);
        sim_debug(DEBUG_DATAIO, dptr, "IMP %03o DATI %012llo %08x PC=%o\n",
                 dev, *data, (uint32)(*data >> 4), PC);
        if (uptr->ILEN != 0)
            uptr->STATUS |= IMPIB;
        sim_activate(uptr, 100);
        break;
    }

    check_interrupts (uptr);
    return SCPE_OK;
}

#if KL
/* Handle KL style interrupt vectors for ITS */
t_addr
imp_devirq(uint32 dev, t_addr addr) {
    if ((cpu_unit[0].flags & UNIT_ITSPAGE) != 0 && (imp_data.pia & 7) == 1) {
        if (imp_unit[0].STATUS & IMPID && (imp_unit[0].STATUS & IMPLW) == 0)
            return 070|RSIGN;
        if (imp_unit[0].STATUS & IMPOD)
            return 072|RSIGN;
    }
    return  addr;
}
#endif
#endif

t_stat imp_srv(UNIT * uptr)
{
#if KS
    DEVICE         *dptr = find_dev_from_unit (uptr);
    struct pdp_dib *dibp = (DIB *)dptr->ctxt;
    t_addr          pa;
    uint64          wd64;
    uint16          wd;

    if (imp_ocsr & CSR_GO && imp_data.sendq == NULL) {
        pa = ((imp_ocsr & CSR_UBA) << 12) | imp_oba;
        switch (GET_DTYPE(uptr->flags)) {
        case TYPE_UNI:
           if (uba_read_npr_word(pa, dibp->uba_ctl, &wd) == 0) {
               imp_ocsr |= CSR_NXM;
               imp_ocsr &= ~CSR_GO;
               uptr->STATUS |= IMPOD;
               if (imp_ocsr & CSR_IE) {
                  uba_set_irq(dibp, dibp->uba_vect + 4);
               }
               sim_debug(DEBUG_DETAIL, &imp_dev, "IMP out npr failed\n");
               return SCPE_OK;
           }
           imp_data.sbuffer[uptr->OPOS >> 3] = (wd & 0377);
           uptr->OPOS += 8;
           imp_data.sbuffer[uptr->OPOS >> 3] = ((wd >> 8) & 0377);
           uptr->OPOS += 8;
           sim_debug(DEBUG_DETAIL, &imp_dev, "IMP send %04x %07o %06o %06o\n",
               wd, pa, imp_owcnt, imp_ocsr);
           break;
        case TYPE_SIMP:
           if (uba_read_npr(pa, dibp->uba_ctl, &wd64) == 0) {
               imp_ocsr |= CSR_NXM;
               imp_ocsr &= ~CSR_GO;
               uptr->STATUS |= IMPOD;
               if (imp_ocsr & CSR_IE) {
                  uba_set_irq(dibp, dibp->uba_vect + 4);
               }
               sim_debug(DEBUG_DETAIL, &imp_dev, "IMP out npr failed\n");
               return SCPE_OK;
           }
           imp_data.sbuffer[uptr->OPOS >> 3] = (wd64 >> 28) & 0377;
           uptr->OPOS += 8;
           imp_data.sbuffer[uptr->OPOS >> 3] = (wd64 >> 20) & 0377;
           uptr->OPOS += 8;
           imp_data.sbuffer[uptr->OPOS >> 3] = (wd64 >> 12) & 0377;
           uptr->OPOS += 8;
           imp_data.sbuffer[uptr->OPOS >> 3] = (wd64 >> 4) & 0377;
           uptr->OPOS += 8;
           imp_oba += 2;
           imp_owcnt++;
           sim_debug(DEBUG_DETAIL, &imp_dev, "IMP send %08llx %07o %06o %06o\n",
               (wd64 >> 4), pa, imp_owcnt, imp_ocsr);
           break;
        }
        imp_oba += 2;
        imp_owcnt++;
        if (imp_oba == 0) {
            imp_ocsr |= CSR_ERR;
            imp_ocsr &= ~CSR_GO;
            uptr->STATUS |= IMPOD;
            if (imp_ocsr & CSR_IE) {
               uba_set_irq(dibp, dibp->uba_vect + 4);
            }
            sim_debug(DEBUG_DETAIL, &imp_dev, "IMP oba overflow\n");
            return SCPE_OK;
        }
        if (imp_owcnt == 0) {
            imp_ocsr |= CSR_RDY;
            imp_ocsr &= ~CSR_GO;
            uptr->STATUS |= IMPOD;
            if (imp_ocsr & CSR_IE) {
               uba_set_irq(dibp, dibp->uba_vect + 4);
            }
            if (imp_ocsr & CSR_ELB) {
                sim_debug(DEBUG_DETAIL, &imp_dev, "IMP send\n");

                imp_send_packet (&imp_data, uptr->OPOS >> 3);
                memset(imp_data.sbuffer, 0, ETH_FRAME_SIZE);
                uptr->OPOS = 0;
            }
        }
    }
    if (uptr->STATUS & IMPIB && imp_icsr & CSR_GO) {
        pa = ((imp_icsr & CSR_UBA) << 12) | imp_iba;
        if (imp_iba == 0) {
            imp_icsr |= CSR_ERR;
            imp_icsr &= ~CSR_GO;
            uptr->STATUS |= IMPID;
            if (imp_icsr & CSR_IE) {
               uba_set_irq(dibp, dibp->uba_vect);
            }
            sim_debug(DEBUG_DETAIL, &imp_dev, "IMP oba overflow\n");
            return SCPE_OK;
        }
        switch (GET_DTYPE(uptr->flags)) {
        case TYPE_UNI:
           wd = imp_data.rbuffer[uptr->IPOS >> 3] << 8;
           uptr->IPOS += 8;
           wd |= imp_data.rbuffer[uptr->IPOS >> 3];
           uptr->IPOS += 8;
           if (uba_write_npr_word(pa, dibp->uba_ctl, wd) == 0) {
               imp_icsr |= CSR_NXM;
               imp_icsr &= ~CSR_GO;
               uptr->STATUS |= IMPID;
               if (imp_icsr & CSR_IE) {
                  uba_set_irq(dibp, dibp->uba_vect);
               }
               sim_debug(DEBUG_DETAIL, &imp_dev, "IMP in npr failed\n");
               return SCPE_OK;
           }
           sim_debug(DEBUG_DETAIL, &imp_dev, "IMP rec %04x %07o %06o %06o\n",
               wd, pa, imp_owcnt, imp_ocsr);
           break;
        case TYPE_SIMP:
           wd64 = (uint64)imp_data.rbuffer[uptr->IPOS >> 3] << 28;
           uptr->IPOS += 8;
           wd64 |= (uint64)imp_data.rbuffer[uptr->IPOS >> 3] << 20;
           uptr->IPOS += 8;
           wd64 |= (uint64)imp_data.rbuffer[uptr->IPOS >> 3] << 12;
           uptr->IPOS += 8;
           wd64 |= (uint64)imp_data.rbuffer[uptr->IPOS >> 3] << 4;
           uptr->IPOS += 8;
           if (uba_write_npr(pa, dibp->uba_ctl, wd64) == 0) {
               imp_ocsr |= CSR_NXM;
               imp_ocsr &= ~CSR_GO;
               uptr->STATUS |= IMPID;
               if (imp_icsr & CSR_IE) {
                  uba_set_irq(dibp, dibp->uba_vect);
               }
               sim_debug(DEBUG_DETAIL, &imp_dev, "IMP out npr failed\n");
               return SCPE_OK;
           }
           imp_iba += 2;
           imp_iwcnt++;
           sim_debug(DEBUG_DETAIL, &imp_dev, "IMP rec %08llx %07o %06o %06o\n",
               (wd64 >> 4), pa, imp_owcnt, imp_ocsr);
           break;
        }
        imp_iba += 2;
        imp_iwcnt++;
        if (uptr->IPOS > uptr->ILEN) {
            imp_icsr |= CSR_EOM|CSR_RDY;
            imp_icsr &= ~CSR_GO;
            uptr->STATUS |= IMPID|IMPLW;
            uptr->STATUS &= ~IMPIB;
            if (imp_icsr & CSR_IE) {
               uba_set_irq(dibp, dibp->uba_vect);
            }
            uptr->ILEN = 0;
        }
        if (imp_iwcnt == 0) {
            imp_icsr |= CSR_RDY;
            imp_icsr &= ~CSR_GO;
            uptr->STATUS |= IMPID;
            if (imp_icsr & CSR_IE) {
               uba_set_irq(dibp, dibp->uba_vect);
            }
        }
    }
    if (imp_ocsr & CSR_GO || imp_icsr & CSR_GO)
        sim_activate(uptr, 100);
#else
    int     i;
    int     l;

    if (uptr->STATUS & IMPOB && imp_data.sendq == NULL) {
        if (imp_data.obits == 32)
           imp_data.obuf >>= 4;
        for (i = imp_data.obits - 1; i >= 0; i--) {
            imp_data.sbuffer[uptr->OPOS>>3] |=
                  ((imp_data.obuf >> i) & 1) << (7-(uptr->OPOS & 7));
            uptr->OPOS++;
        }
        if (uptr->STATUS & IMPLHW) {
            imp_send_packet (&imp_data, uptr->OPOS >> 3);
            /* Allow room for ethernet header for later */
            memset(imp_data.sbuffer, 0, ETH_FRAME_SIZE);
            uptr->OPOS = 0;
            uptr->STATUS &= ~IMPLHW;
        }
        uptr->STATUS &= ~IMPOB;
        uptr->STATUS |= IMPOD;
        check_interrupts (uptr);
    }
    if (uptr->STATUS & IMPIB) {
        uptr->STATUS &= ~(IMPIB|IMPLW);
        imp_data.ibuf = 0;
        l = (uptr->STATUS & IMPI32) ? 4 : 0;
        for (i = 35; i >= l; i--) {
             if ((imp_data.rbuffer[uptr->IPOS>>3] >> (7-(uptr->IPOS & 7))) & 1)
                 imp_data.ibuf |= ((uint64)1) << i;
             uptr->IPOS++;
             if (uptr->IPOS > uptr->ILEN) {
                uptr->STATUS |= IMPLW;
                uptr->ILEN = 0;
                break;
             }
        }
        uptr->STATUS |= IMPID;
        check_interrupts (uptr);
    }
#endif
    if (uptr->ILEN == 0 && (uptr->STATUS & (IMPIB|IMPID)) == 0)
        imp_packet_in(&imp_data);
    return SCPE_OK;
}

void
ip_checksum(uint8 *chksum, uint8 *ptr, int len)
{
   /*
    * Compute Internet Checksum for "count" bytes
    *         beginning at location "addr".
    */
   int32  sum = 0;

   while( len > 1 )  {
      /*  This is the inner loop */
      sum += (ptr[0]<<8)+ptr[1];
      ptr+=2;
      len -= 2;
   }

   /*  Add left-over byte, if any */
   if( len > 0 )
      sum += ptr[0]<<8;

    /*  Fold 32-bit sum to 16 bits */
    while (sum>>16)
       sum = (sum & 0xffff) + (sum >> 16);
    sum=(~sum & 0xffff);
    chksum[0]=(sum>>8) & 0xff;
    chksum[1]=sum & 0xff;
}


/*
 * Update the checksum based on code from RFC1631
 */
void
checksumadjust(uint8 *chksum, uint8 *optr,
   int olen, uint8 *nptr, int nlen)
   /* assuming: unsigned char is 8 bits, long is 32 bits.
     - chksum points to the chksum in the packet
     - optr points to the old data in the packet
     - nptr points to the new data in the packet
     - even number of octets updated.
   */
{
    int32 sum, old, new_sum;
    sum=(chksum[0]<<8)+chksum[1];
    sum=(~sum & 0xffff);
    while (olen > 1) {
        old=(optr[0]<<8)+optr[1];
        optr+=2;
        sum-=old & 0xffff;
        if (sum<=0) { sum--; sum&=0xffff; }
        olen-=2;
    }
    if (olen > 0) {
        old=optr[0]<<8;
        sum-=old & 0xffff;
        if (sum<=0) { sum--; sum&=0xffff; }
    }
    while (nlen > 1) {
        new_sum=(nptr[0]<<8)+nptr[1];
        nptr+=2;
        sum+=new_sum & 0xffff;
        if (sum & 0x10000) { sum++; sum&=0xffff; }
        nlen-=2;
    }
    if (nlen > 0) {
        new_sum=(nptr[0]<<8);
        sum+=new_sum & 0xffff;
        if (sum & 0x10000) { sum++; sum&=0xffff; }
    }
    sum=(~sum & 0xffff);
    chksum[0]=sum>>8;
    chksum[1]=sum & 0xff;
}

t_stat imp_eth_srv(UNIT * uptr)
{
    sim_clock_coschedule(uptr, 1000);              /* continue poll */

    imp_timer_task(&imp_data);
    if (uptr->ILEN == 0 && (uptr->STATUS & (IMPIB|IMPID)) == 0)
        imp_packet_in(&imp_data);

    if (imp_data.init_state >= 3 && imp_data.init_state < 6) {
       if (imp_unit[0].flags & UNIT_DHCP &&
           imp_data.dhcp_state != DHCP_STATE_BOUND &&
           imp_data.dhcp_state != DHCP_STATE_REBINDING &&
           imp_data.dhcp_state != DHCP_STATE_RENEWING)
           return SCPE_OK;
       sim_debug(DEBUG_DETAIL, &imp_dev, "IMP init Nop %d\n",
                 imp_data.init_state);
       if (imp_unit[0].ILEN == 0) {
              /* Queue up a nop packet */
#if KS
              imp_data.rbuffer[0] = 0xf;
              imp_data.rbuffer[3] = 4;
#else
              imp_data.rbuffer[0] = 0x4;
#endif
              imp_data.rbuffer[1] = (ntohl(imp_data.hostip) >> 24) & 0xff;
              imp_data.rbuffer[5] = (ntohl(imp_data.hostip) >> 16) & 0xff;
              imp_data.rbuffer[6] = (ntohl(imp_data.hostip) >> 8) & 0xff;
              imp_data.rbuffer[7] = ntohl(imp_data.hostip) & 0xff;
              imp_unit[0].STATUS |= IMPIB;
              imp_unit[0].IPOS = 0;
              imp_unit[0].ILEN = 12*8;
              imp_data.init_state++;
              sim_debug(DEBUG_DETAIL, &imp_dev, "IMP Send Nop %d\n",
                       imp_data.init_state);
              check_interrupts (&imp_unit[0]);
              sim_activate(&imp_unit[0], 100);
        }
    }
    return SCPE_OK;
}

void
imp_timer_task(struct imp_device *imp)
{
    struct imp_packet  *nq = NULL;                /* New send queue */
    int                 n;

    /* Scan through adjusted ports and remove old ones */
    for (n = 0; n < 64; n++) {
        if (imp->port_map[n].cls_tim > 0) {
            if (--imp->port_map[n].cls_tim == 0) {
                imp->port_map[n].dport = 0;
                imp->port_map[n].sport = 0;
                imp->port_map[n].adj = 0;
            }
        }
    }

    /* Scan the send queue and see if any packets have timed out */
    while (imp->sendq != NULL) {
         struct imp_packet *temp = imp->sendq;
         imp->sendq = temp->next;

         if (--temp->life == 0) {
            imp_free_packet(imp, temp);
            sim_debug(DEBUG_DETAIL, &imp_dev,
                        "IMP packet timed out %08x\n", temp->dest);
         } else {
            /* Not yet, put back on queue */
            temp->next = nq;
            nq = temp;
         }
     }
     imp->sendq = nq;
}

t_stat imp_tim_srv(UNIT * uptr)
{
    sim_activate_after(uptr, 1000000);              /* come back once per second */

    imp_dhcp_timer(&imp_data);
    imp_arp_age(&imp_data);
#if KS
    imp_icsr &= ~CSR_HR;
#endif
    return SCPE_OK;
}

void
imp_packet_in(struct imp_device *imp)
{
   ETH_PACK                read_buffer;
   struct imp_eth_hdr     *hdr;
   int                     type;
   int                     n;
   int                     pad;

   if (eth_read (&imp_data.etherface, &read_buffer, NULL) <= 0) {
       /* Any pending packet notifications? */
       if (imp->rfnm_count != 0) {
           /* Create RFNM packet */
           memset(&imp->rbuffer[0], 0, 256);
           imp->rbuffer[0] = 0xf;
           imp->rbuffer[3] = 4;
           imp_unit[0].STATUS |= IMPIB;
           imp_unit[0].IPOS = 0;
           imp_unit[0].ILEN = 12*8;
           if (!sim_is_active(&imp_unit[0]))
               sim_activate(&imp_unit[0], 100);
           imp->rfnm_count--;
       }
       return;
   }
   imp_packet_debug(imp, "Received", &read_buffer);
   hdr = (struct imp_eth_hdr *)(&read_buffer.msg[0]);
   type = ntohs(hdr->type);
   if (type == ETHTYPE_ARP) {
       imp_arp_arpin(imp, &read_buffer);
   } else if (type == ETHTYPE_IP) {
       struct ip           *ip_hdr =
              (struct ip *)(&read_buffer.msg[sizeof(struct imp_eth_hdr)]);
       /* Process DHCP is this is IP broadcast */
       if (ip_hdr->ip_dst == broadcast_ipaddr ||
                memcmp(&hdr->dest, &imp->mac, 6) == 0) {
           uint8   *payload = (uint8 *)(&read_buffer.msg[sizeof(struct imp_eth_hdr) +
                                       (ip_hdr->ip_v_hl & 0xf) * 4]);
           struct udp *udp_hdr = (struct udp *)payload;
           /* Check for DHCP traffic */
           if (ip_hdr->ip_p == UDP_PROTO &&
               ntohs(udp_hdr->udp_dport) == DHCP_UDP_PORT_CLIENT &&
               ntohs(udp_hdr->udp_sport) == DHCP_UDP_PORT_SERVER) {
              imp_do_dhcp_client(imp, &read_buffer);
              return;
           }
       }
       /* Process as IP if it is for us */
       if (ip_hdr->ip_dst == imp_data.ip || ip_hdr->ip_dst == 0) {
           /* Add mac address since we will probably need it later */
           imp_arp_update(imp, ip_hdr->ip_src, hdr->src, 0);
           /* Clear beginning of message */
           memset(&imp->rbuffer[0], 0, 256);
           imp->rbuffer[0] = 0xf;
           imp->rbuffer[3] = 0;
#if KS
           imp->rbuffer[1] = (ntohl(ip_hdr->ip_src) >> 24) & 0xff;
           imp->rbuffer[5] = (ntohl(ip_hdr->ip_src) >> 16) & 0xff;
           imp->rbuffer[6] = (ntohl(ip_hdr->ip_src) >> 8) & 0xff;
           imp->rbuffer[7] = ntohl(ip_hdr->ip_src) & 0xff;
#else
           imp->rbuffer[7] = 14;
#endif
           imp->rbuffer[8] = 0233;
#if !KS
           imp->rbuffer[18] = 0;
           imp->rbuffer[19] = 0x80;
           imp->rbuffer[21] = 0x30;
#endif

           /* Copy message over */
           pad = 12 + (imp->padding / 8);
           n = read_buffer.len;
           memcpy(&imp->rbuffer[pad], ip_hdr ,n);
           ip_hdr = (struct ip *)(&imp->rbuffer[pad]);
            /* Re-point IP header to copy packet */
            /*
             * If local IP defined, change destination to ip,
             * and update checksum
             */
           if (ip_hdr->ip_dst == imp_data.ip && imp_data.hostip != 0) {
               uint8   *payload = (uint8 *)(&imp->rbuffer[pad +
                                           (ip_hdr->ip_v_hl & 0xf) * 4]);
               /* If TCP packet update the TCP checksum */
               if (ip_hdr->ip_p == TCP_PROTO) {
                   struct tcp *tcp_hdr = (struct tcp *)payload;
                   uint16       dport = ntohs(tcp_hdr->tcp_dport);
                   uint16       sport = ntohs(tcp_hdr->tcp_sport);
                   int          thl = ((ntohs(tcp_hdr->flags) >> 12) & 0xf) * 4;
                   int          hl = (ip_hdr->ip_v_hl & 0xf) * 4;
                   uint8       *tcp_payload = &imp->rbuffer[
                            sizeof(struct imp_eth_hdr) + hl + thl];
                   checksumadjust((uint8 *)&tcp_hdr->chksum,
                              (uint8 *)(&ip_hdr->ip_dst), sizeof(in_addr_T),
                              (uint8 *)(&imp_data.hostip), sizeof(in_addr_T));
                   if ((ntohs(tcp_hdr->flags) & TCP_FL_ACK) != 0) {
                       for (n = 0; n < 64; n++) {
                           if (imp->port_map[n].sport == sport &&
                               imp->port_map[n].dport == dport) {
                               /* Check if SYN */
                               if (ntohs(tcp_hdr->flags) & TCP_FL_SYN) {
                                   imp->port_map[n].sport = 0;
                                   imp->port_map[n].dport = 0;
                                   imp->port_map[n].adj = 0;
                               } else {
                                   uint32   new_seq = ntohl(tcp_hdr->ack);
                                   if (new_seq > imp->port_map[n].lseq) {
                                       new_seq = htonl(new_seq - imp->port_map[n].adj);
                                       checksumadjust((uint8 *)&tcp_hdr->chksum,
                                               (uint8 *)(&tcp_hdr->ack), 4,
                                               (uint8 *)(&new_seq), 4);
                                       tcp_hdr->ack = new_seq;
                                   }
                               }
                               if (ntohs(tcp_hdr->flags) & TCP_FL_FIN)
                                   imp->port_map[n].cls_tim = 100;
                               break;
                           }
                       }
                    }
                    /* Check if receiving to FTP */
                    if (sport == 21 && strncmp((CONST char *)&tcp_payload[0], "PORT ", 5) == 0) {
                        /* We need to translate the IP address to new port number. */
                        int     l = ntohs(ip_hdr->ip_len) - thl - hl;
                        uint32  nip = ntohl(imp->hostip);
                        int     nlen;
                        int     i;
                        char    port_buffer[100];
                        struct udp_hdr     udp_hdr;
                       if (l > 1500)
                           l = 1500;
                       /* Count out 4 commas */
                       for (i = nlen = 0; i < l && nlen < 4; i++) {
                          if (tcp_payload[i] == ',')
                             nlen++;
                       }
                       nlen = sprintf(port_buffer, "PORT %d,%d,%d,%d,",
                            (nip >> 24) & 0xFF, (nip >> 16) & 0xFF,
                            (nip >> 8) & 0xFF, nip & 0xff);
                       /* Copy over rest of string */
                       while(i < l) {
                           port_buffer[nlen++] = tcp_payload[i++];
                       }
                       port_buffer[nlen] = '\0';
                       memcpy(tcp_payload, port_buffer, nlen);
                       /* Check if we need to update the sequence numbers */
                       if (nlen != l && (ntohs(tcp_hdr->flags) & TCP_FL_SYN) == 0) {
                           int n = -1;
                           /* See if we need to change the sequence number */
                           for (i = 0; i < 64; i++) {
                               if (imp->port_map[i].sport == sport &&
                                   imp->port_map[i].dport == dport) {
                                   n = i;
                                   break;
                               }
                               if (n < 0 && imp->port_map[i].dport == 0)
                                   n = 0;
                           }
                           if (n >= 0) {
                               imp->port_map[n].dport = dport;
                               imp->port_map[n].sport = sport;
                               imp->port_map[n].adj += nlen - l;
                               imp->port_map[n].cls_tim = 0;
                               imp->port_map[n].lseq = ntohl(tcp_hdr->seq);
                           }
                       }
                       /* Now we need to update the checksums */
                       tcp_hdr->chksum = 0;
                       ip_hdr->ip_len = htons(nlen + thl + hl);
                       ip_checksum((uint8 *)&tcp_hdr->chksum, (uint8 *)tcp_hdr,
                               nlen + thl);
                       udp_hdr.ip_src = ip_hdr->ip_src;
                       udp_hdr.ip_dst = imp->hostip;
                       udp_hdr.zero = 0;
                       udp_hdr.proto = TCP_PROTO;
                       udp_hdr.hlen = htons(nlen + thl);
                       checksumadjust((uint8 *)&tcp_hdr->chksum, (uint8 *)(&udp_hdr), 0,
                            (uint8 *)(&udp_hdr), sizeof(udp_hdr));
                       ip_hdr->ip_sum = 0;
                       ip_checksum((uint8 *)(&ip_hdr->ip_sum), (uint8 *)ip_hdr, 20);
                   }
               /* Check if UDP */
               } else if (ip_hdr->ip_p == UDP_PROTO) {
                    struct udp *udp_hdr = (struct udp *)payload;
                    if (ip_hdr->ip_p == UDP_PROTO &&
                        htons(udp_hdr->udp_dport) == DHCP_UDP_PORT_CLIENT &&
                        htons(udp_hdr->udp_sport) == DHCP_UDP_PORT_SERVER) {
                        imp_do_dhcp_client(imp, &read_buffer);
                        return;
                    }
                    checksumadjust((uint8 *)&udp_hdr->chksum,
                              (uint8 *)(&ip_hdr->ip_src), sizeof(in_addr_T),
                              (uint8 *)(&imp_data.hostip), sizeof(in_addr_T));
               /* Lastly check if ICMP */
               } else if (ip_hdr->ip_p == ICMP_PROTO) {
                    struct icmp *icmp_hdr = (struct icmp *)payload;
                    if ((icmp_hdr->type != 0) &&   /*     Not Echo Reply */
                        (icmp_hdr->type != 8))     /* and Not Echo */
                        checksumadjust((uint8 *)&icmp_hdr->chksum,
                                  (uint8 *)(&ip_hdr->ip_src), sizeof(in_addr_T),
                                  (uint8 *)(&imp_data.hostip), sizeof(in_addr_T));
               }
               checksumadjust((uint8 *)&ip_hdr->ip_sum,
                            (uint8 *)(&ip_hdr->ip_dst), sizeof(in_addr_T),
                            (uint8 *)(&imp_data.hostip), sizeof(in_addr_T));
               ip_hdr->ip_dst = imp_data.hostip;
           }

           /* If we are not initializing queue it up for host */
           if (imp_data.init_state >= 6) {
               n = pad + ntohs(ip_hdr->ip_len);
               imp_unit[0].STATUS |= IMPIB;
               imp_unit[0].IPOS = 0;
               imp_unit[0].ILEN = n*8;
               imp->rbuffer[1] = 0;
               imp->rbuffer[5] = ntohl(ip_hdr->ip_src) & 0xff;
               imp->rbuffer[6] = (ntohl(ip_hdr->ip_src) >> 16) & 0xff;
               imp->rbuffer[7] = (ntohl(ip_hdr->ip_src) >> 8) & 0xff;
               imp->rbuffer[10] = (n >> 8) & 0xff;
               imp->rbuffer[11] = n & 0xff;
           }
           if (!sim_is_active(&imp_unit[0]))
               sim_activate(&imp_unit[0], 100);
       }
         /* Otherwise just ignore it */
   }
}

void
imp_send_packet (struct imp_device *imp, int len)
{
    ETH_PACK   write_buffer;
    int        i;
    UNIT      *uptr = &imp_unit[1];
    int        n;
    int        st;
    int        lk;
    int        mt;

    lk = 0;
    n = len;
    switch (imp->sbuffer[0] & 0xF) {
    default:
       /* Send back invalid leader message */
       sim_printf("Invalid header\n");
       return;
    case 0x0:
       mt = 0;
       st = imp->sbuffer[3] & 0xf;
       lk = 0233;
       break;
    case 0x4:
       mt = 4;
       st = imp->sbuffer[3] & 0xf;
       break;
    case 0xf:
       st = imp->sbuffer[9] & 0xf;
       lk = imp->sbuffer[8];
       mt = imp->sbuffer[3];
       n = (imp->sbuffer[10] << 8) + (imp->sbuffer[11]);
       break;
    }
    sim_debug(DEBUG_DETAIL, &imp_dev,
        "IMP packet H=%x Type=%d ht=%d dh=%d imp=%d lk=%o %d st=%d Len=%d mt=%d\n",
       imp->sbuffer[0],
         imp->sbuffer[3], imp->sbuffer[4], imp->sbuffer[5],
         (imp->sbuffer[6] * 256) + imp->sbuffer[7],
         lk, imp->sbuffer[9] >> 4, st, n, mt);
    switch(mt) {
    case 0:      /* Regular packet */
           switch(st) {
           case 0: /* Regular */
           case 1: /* Refusable */
                  if (lk == 0233) {
                     i = 12 + (imp->padding / 8);
                     n = len - i;
                     memcpy(&write_buffer.msg[sizeof(struct imp_eth_hdr)],
                            &imp->sbuffer[i], n);
                     write_buffer.len = n+sizeof(struct imp_eth_hdr);
                     imp_packet_out(imp, &write_buffer);
                  }
                  break;
           case 4:      /* Nop */
                  if (imp->init_state < 3)
                     imp->init_state++;
                  imp->padding = 0;
                  sim_debug(DEBUG_DETAIL, &imp_dev,
                               "IMP recieve Nop %d padding= %d\n",
                                imp->init_state, imp->padding);
                  sim_activate(uptr, tmxr_poll); /* Start reciever task */
                  break;
           case 2: /* Getting ready */
           case 3: /* Uncontrolled */
           default:
                  break;
           }
           break;
    case 1:      /* Error */
           break;
    case 2:      /* Host going down */
           break;
    case 4:      /* Nop */
           if (imp->init_state < 3)
              imp->init_state++;
           imp->padding = st * 16;
           sim_debug(DEBUG_DETAIL, &imp_dev,
                        "IMP recieve Nop %d padding= %d\n",
                         imp->init_state, imp->padding);
           sim_activate(uptr, tmxr_poll); /* Start reciever task */
           break;
    case 8:      /* Error with Message */
           break;
    default:
           break;
    }
    return;
}

/*
 * Check if this packet can be sent to given IP.
 * If it can we fill in the mac address and return false.
 * If we can't we need to queue up and send a ARP packet and return true.
 */
void
imp_packet_out(struct imp_device *imp, ETH_PACK *packet) {
    struct ip_hdr     *pkt = (struct ip_hdr *)(&packet->msg[0]);
    struct imp_packet *send;
    struct arp_entry  *tabptr;
    in_addr_T          ipaddr;
    int                i;

    /* If local IP defined, change source to ip, and update checksum */
    if (imp->hostip != 0) {
       int          hl = (pkt->iphdr.ip_v_hl & 0xf) * 4;
       uint8        *payload = (uint8 *)(&packet->msg[
                            sizeof(struct imp_eth_hdr) + hl]);
       /* If TCP packet update the TCP checksum */
       if (pkt->iphdr.ip_p == TCP_PROTO) {
           struct tcp  *tcp_hdr = (struct tcp *)payload;
           int          thl = ((ntohs(tcp_hdr->flags) >> 12) & 0xf) * 4;
           uint16       sport = ntohs(tcp_hdr->tcp_sport);
           uint16       dport = ntohs(tcp_hdr->tcp_dport);
           uint8       *tcp_payload = &packet->msg[
                    sizeof(struct imp_eth_hdr) + hl + thl];
           /* Update pseudo header checksum */
           checksumadjust((uint8 *)&tcp_hdr->chksum,
                       (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T),
                       (uint8 *)(&imp->ip), sizeof(in_addr_T));
           /* See if we need to change the sequence number */
           for (i = 0; i < 64; i++) {
               if (imp->port_map[i].sport == sport &&
                   imp->port_map[i].dport == dport) {
                   /* Check if SYN */
                   if (ntohs(tcp_hdr->flags) & TCP_FL_SYN) {
                       imp->port_map[i].sport = 0;
                       imp->port_map[i].dport = 0;
                       imp->port_map[i].adj = 0;
                   } else {
                       uint32   new_seq = ntohl(tcp_hdr->seq);
                       if (new_seq > imp->port_map[i].lseq) {
                           new_seq = htonl(new_seq + imp->port_map[i].adj);
                           checksumadjust((uint8 *)&tcp_hdr->chksum,
                                   (uint8 *)(&tcp_hdr->seq), 4,
                                   (uint8 *)(&new_seq), 4);
                           tcp_hdr->seq = new_seq;
                       }
                   }
                   if (ntohs(tcp_hdr->flags) & TCP_FL_FIN)
                       imp->port_map[i].cls_tim = 100;
                   break;
               }
           }
           /* Check if sending to FTP */
           if (dport == 21 && strncmp((CONST char *)&tcp_payload[0], "PORT ", 5) == 0) {
               /* We need to translate the IP address to new port number. */
               int     l = ntohs(pkt->iphdr.ip_len) - thl - hl;
               uint32  nip = ntohl(imp->ip);
               int     nlen;
               char    port_buffer[100];
               struct udp_hdr     udp_hdr;
               /* Count out 4 commas */
               for (i = nlen = 0; i < l && nlen < 4; i++) {
                  if (tcp_payload[i] == ',')
                     nlen++;
               }
               nlen = sprintf(port_buffer, "PORT %d,%d,%d,%d,",
                    (nip >> 24) & 0xFF, (nip >> 16) & 0xFF,
                    (nip >> 8) & 0xFF, nip&0xff);
               /* Copy over rest of string */
               while(i < l) {
                   port_buffer[nlen++] = tcp_payload[i++];
               }
               port_buffer[nlen] = '\0';
               memcpy(tcp_payload, port_buffer, nlen);
               /* Check if we need to update the sequence numbers */
               if (nlen != l && (ntohs(tcp_hdr->flags) & TCP_FL_SYN) == 0) {
                   int n = -1;
                   /* See if we need to change the sequence number */
                   for (i = 0; i < 64; i++) {
                       if (imp->port_map[i].sport == sport &&
                           imp->port_map[i].dport == dport) {
                           n = i;
                           break;
                       }
                       if (n < 0 && imp->port_map[i].dport == 0)
                           n = 0;
                   }
                   if (n >= 0) {
                       imp->port_map[n].dport = dport;
                       imp->port_map[n].sport = sport;
                       imp->port_map[n].adj += nlen - l;
                       imp->port_map[n].cls_tim = 0;
                       imp->port_map[n].lseq = ntohl(tcp_hdr->seq);
                   }
               }
               /* Now we need to update the checksums */
               tcp_hdr->chksum = 0;
               pkt->iphdr.ip_len = htons(nlen + thl + hl);
               ip_checksum((uint8 *)&tcp_hdr->chksum, (uint8 *)tcp_hdr,
                       nlen + thl);
               udp_hdr.ip_src = imp->ip;
               udp_hdr.ip_dst = pkt->iphdr.ip_dst;
               udp_hdr.zero = 0;
               udp_hdr.proto = TCP_PROTO;
               udp_hdr.hlen = htons(nlen + thl);
               checksumadjust((uint8 *)&tcp_hdr->chksum, (uint8 *)(&udp_hdr), 0,
                    (uint8 *)(&udp_hdr), sizeof(udp_hdr));
               pkt->iphdr.ip_sum = 0;
               ip_checksum((uint8 *)(&pkt->iphdr.ip_sum), (uint8 *)&pkt->iphdr, 20);
               packet->len = nlen + thl + hl +  sizeof(struct imp_eth_hdr);
           }
       /* Check if UDP */
       } else if (pkt->iphdr.ip_p == UDP_PROTO) {
             struct udp *udp_hdr = (struct udp *)payload;
             checksumadjust((uint8 *)&udp_hdr->chksum,
                  (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T),
                  (uint8 *)(&imp->ip), sizeof(in_addr_T));
        /* Lastly check if ICMP */
       } else if (pkt->iphdr.ip_p == ICMP_PROTO) {
             struct icmp *icmp_hdr = (struct icmp *)payload;
             if ((icmp_hdr->type != 0) &&   /*     Not Echo Reply */
                 (icmp_hdr->type != 8))     /* and Not Echo */
                 checksumadjust((uint8 *)&icmp_hdr->chksum,
                      (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T),
                      (uint8 *)(&imp->ip), sizeof(in_addr_T));
       }
       /* Lastly update the header and IP address */
       checksumadjust((uint8 *)&pkt->iphdr.ip_sum,
                 (uint8 *)(&pkt->iphdr.ip_src), sizeof(in_addr_T),
                 (uint8 *)(&imp->ip), sizeof(in_addr_T));
       pkt->iphdr.ip_src = imp->ip;
    }

    /* Try to send the packed */
    ipaddr = pkt->iphdr.ip_dst;
    packet->len = sizeof(struct imp_eth_hdr) + ntohs(pkt->iphdr.ip_len);
    /* Enforce minimum packet size */
    while (packet->len < 60)
       packet->msg[packet->len++] = 0;

    /* Check if on our subnet */
    if ((imp->ip & imp->ip_mask) != (ipaddr & imp->ip_mask))
        ipaddr = imp->gwip;

    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        tabptr = &imp->arp_table[i];
        if (ipaddr == tabptr->ipaddr) {
            memcpy(&pkt->ethhdr.dest, &tabptr->ethaddr, 6);
            memcpy(&pkt->ethhdr.src, &imp->mac, 6);
            pkt->ethhdr.type = htons(ETHTYPE_IP);
            imp_write(imp, packet);
            imp->rfnm_count++;
            return;
         }
    }

    /* Queue packet for later send */
    send = imp_get_packet(imp);
    send->next = imp->sendq;
    imp->sendq = send;
    send->packet.len = packet->len;
    send->life = 1000;
    send->dest = pkt->iphdr.ip_dst;
    memcpy(&send->packet.msg[0], pkt, send->packet.len);

    /* We did not find it, so construct and send an ARP packet */
    imp_arp_arpout(imp, ipaddr);
}


void imp_packet_debug(struct imp_device *imp, const char *action, ETH_PACK *packet) {
    struct imp_eth_hdr *eth = (struct imp_eth_hdr *)&packet->msg[0];
    struct arp_hdr     *arp = (struct arp_hdr *)eth;
    struct ip          *ip = (struct ip *)&packet->msg[sizeof(struct imp_eth_hdr)];
    struct udp         *udp;
    struct tcp         *tcp;
    struct icmp        *icmp;
    uint8              *payload;
    struct in_addr     ipaddr;
    size_t             len;
    int                flag;
    char               src_ip[20];
    char               dst_ip[20];
    char               src_port[8];
    char               dst_port[8];
    char               mac_buf[20];
    char               flags[64];
    static struct tcp_flag_bits {
        const char *name;
        uint16      bitmask;
        } bits[] = {
            {"FIN", TCP_FL_FIN},
            {"SYN", TCP_FL_SYN},
            {"RST", TCP_FL_RST},
            {"PSH", TCP_FL_PSH},
            {"ACK", TCP_FL_ACK},
            {"URG", TCP_FL_URG},
            {NULL, 0}
        };
    static const char *icmp_types[] = {
        "Echo Reply",                                   // Type 0
        "Type 1 - Unassigned",
        "Type 2 - Unassigned",
        "Destination Unreachable",                      // Type 3
        "Source Quench (Deprecated)",                   // Type 4
        "Redirect",                                     // Type 5
        "Type 6 - Alternate Host Address (Deprecated)",
        "Type 7 - Unassigned",
        "Echo Request",                                 // Type 8
        "Router Advertisement",                         // Type 9
        "Router Selection",                             // Type 10
        "Time Exceeded",                                // Type 11
        "Type 12 - Parameter Problem",
        "Type 13 - Timestamp",
        "Type 14 - Timestamp Reply",
        "Type 15 - Information Request (Deprecated)",
        "Type 16 - Information Reply (Deprecated)",
        "Type 17 - Address Mask Request (Deprecated)",
        "Type 18 - Address Mask Reply (Deprecated)",
        "Type 19 - Reserved (for Security)",
        "Type 20 - Reserved (for Robustness Experiment)",
        "Type 21 - Reserved (for Robustness Experiment)",
        "Type 22 - Reserved (for Robustness Experiment)",
        "Type 23 - Reserved (for Robustness Experiment)",
        "Type 24 - Reserved (for Robustness Experiment)",
        "Type 25 - Reserved (for Robustness Experiment)",
        "Type 26 - Reserved (for Robustness Experiment)",
        "Type 27 - Reserved (for Robustness Experiment)",
        "Type 28 - Reserved (for Robustness Experiment)",
        "Type 29 - Reserved (for Robustness Experiment)",
        "Type 30 - Traceroute (Deprecated)",
        "Type 31 - Datagram Conversion Error (Deprecated)",
        "Type 32 - Mobile Host Redirect (Deprecated)",
        "Type 33 - IPv6 Where-Are-You (Deprecated)",
        "Type 34 - IPv6 I-Am-Here (Deprecated)",
        "Type 35 - Mobile Registration Request (Deprecated)",
        "Type 36 - Mobile Registration Reply (Deprecated)",
        "Type 37 - Domain Name Request (Deprecated)",
        "Type 38 - Domain Name Reply (Deprecated)",
        "Type 39 - SKIP (Deprecated)",
        "Type 40 - Photuris",
        "Type 41 - ICMP messages utilized by experimental mobility protocols such as Seamoby",
        "Type 42 - Extended Echo Request",
        "Type 43 - Extended Echo Reply"
    };

    if (ntohs(eth->type) == ETHTYPE_ARP) {
        struct in_addr in_addr;
        const char *arp_op = (ARP_REQUEST == ntohs(arp->opcode)) ? "REQUEST" : ((ARP_REPLY == ntohs(arp->opcode)) ? "REPLY" : "Unknown");
        char eth_src[20], eth_dst[20];
        char arp_shwaddr[20], arp_dhwaddr[20];
        char arp_sipaddr[20], arp_dipaddr[20];

        if (!(imp_dev.dctrl & DEBUG_ARP))
            return;
        eth_mac_fmt(arp->ethhdr.src, eth_src);
        eth_mac_fmt(arp->ethhdr.dest, eth_dst);
        eth_mac_fmt(arp->shwaddr, arp_shwaddr);
        memcpy(&in_addr, &arp->sipaddr, sizeof(in_addr));
        strlcpy(arp_sipaddr, ipv4_inet_ntoa(in_addr), sizeof(arp_sipaddr));
        eth_mac_fmt(arp->dhwaddr, arp_dhwaddr);
        memcpy(&in_addr, &arp->dipaddr, sizeof(in_addr));
        strlcpy(arp_dipaddr, ipv4_inet_ntoa(in_addr), sizeof(arp_dipaddr));
        sim_debug(DEBUG_ARP, &imp_dev,
            "%s %s EthDst=%s EthSrc=%s shwaddr=%s sipaddr=%s dhwaddr=%s dipaddr=%s\n",
            action, arp_op, eth_dst, eth_src, arp_shwaddr, arp_sipaddr, arp_dhwaddr, arp_dipaddr);
        return;
    }
    if (ntohs(eth->type) != ETHTYPE_IP)
        return;
    if (!(imp_dev.dctrl & (DEBUG_TCP|DEBUG_UDP|DEBUG_ICMP)))
        return;
    memcpy(&ipaddr, &ip->ip_src, sizeof(ipaddr));
    strlcpy(src_ip, ipv4_inet_ntoa(ipaddr), sizeof(src_ip));
    memcpy(&ipaddr, &ip->ip_dst, sizeof(ipaddr));
    strlcpy(dst_ip, ipv4_inet_ntoa(ipaddr), sizeof(dst_ip));
    payload = (uint8 *)&packet->msg[sizeof(struct imp_eth_hdr) + (ip->ip_v_hl & 0xf) * 4];
    switch (ip->ip_p) {
        case UDP_PROTO:
            udp = (struct udp *)payload;
            snprintf(src_port, sizeof(src_port), "%d", ntohs(udp->udp_sport));
            snprintf(dst_port, sizeof(dst_port), "%d", ntohs(udp->udp_dport));
            sim_debug(DEBUG_UDP, &imp_dev, "%s %d byte packet from %s:%s to %s:%s\n", action,
                ntohs(udp->len), src_ip, src_port, dst_ip, dst_port);
            if ((imp_dev.dctrl & DEBUG_DHCP) &&
                (((ntohs(udp->udp_sport) == DHCP_UDP_PORT_CLIENT) &&
                  (ntohs(udp->udp_dport) == DHCP_UDP_PORT_SERVER)) ||
                 ((ntohs(udp->udp_dport) == DHCP_UDP_PORT_CLIENT) &&
                  (ntohs(udp->udp_sport) == DHCP_UDP_PORT_SERVER)))) {
                struct dhcp         *dhcp = (struct dhcp *)(payload + sizeof(struct udp));
                uint8               *opt = &dhcp->options[0];

                sim_debug(DEBUG_DHCP, &imp_dev, "%s XID=%08X",
                    (dhcp->op == DHCP_BOOTREQUEST) ? "REQUEST" :
                                                     (dhcp->op == DHCP_BOOTREPLY) ? "REPLY" :
                                                                                    "??????",
                    dhcp->xid);
                if (dhcp->ciaddr) {
                    memcpy(&ipaddr, &dhcp->ciaddr, sizeof(ipaddr));
                    sim_debug(DEBUG_DHCP, &imp_dev, ", ciaddr=%s", ipv4_inet_ntoa(ipaddr));
                    }
                if (dhcp->yiaddr) {
                    memcpy(&ipaddr, &dhcp->yiaddr, sizeof(ipaddr));
                    sim_debug(DEBUG_DHCP, &imp_dev, ", yiaddr=%s", ipv4_inet_ntoa(ipaddr));
                    }
                if (dhcp->siaddr) {
                    memcpy(&ipaddr, &dhcp->siaddr, sizeof(ipaddr));
                    sim_debug(DEBUG_DHCP, &imp_dev, ", siaddr=%s", ipv4_inet_ntoa(ipaddr));
                    }
                if (dhcp->giaddr) {
                    memcpy(&ipaddr, &dhcp->giaddr, sizeof(ipaddr));
                    sim_debug(DEBUG_DHCP, &imp_dev, ", giaddr=%s", ipv4_inet_ntoa(ipaddr));
                    }
                eth_mac_fmt(dhcp->chaddr, mac_buf);
                sim_debug(DEBUG_DHCP, &imp_dev, ", chaddr=%s Options: ", mac_buf);
                while (*opt != DHCP_OPTION_END) {
                    int opt_len;
                    u_long numeric;
                    static const char  *opr_names[] = {
                                        "",             "DISCOVER",     "OFFER",        "REQUEST",
                                        "DECLINE",      "ACK",          "NAK",          "RELEASE",
                                        "INFORM"
                                        };


                    switch(*opt++) {
                    case DHCP_OPTION_PAD:
                          break;
                    default:
                          opt_len = *opt++;
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_SUBNET_MASK:
                          opt_len = *opt++;
                          memcpy(&ipaddr, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", mask=%s", ipv4_inet_ntoa(ipaddr));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_ROUTER:
                          opt_len = *opt++;
                          memcpy(&ipaddr, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", router=%s", ipv4_inet_ntoa(ipaddr));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_REQUESTED_IP:
                          opt_len = *opt++;
                          memcpy(&ipaddr, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", requested-ip=%s", ipv4_inet_ntoa(ipaddr));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_LEASE_TIME:
                          opt_len = *opt++;
                          memcpy(&numeric, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", lease-time=%d", ntohl(numeric));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_T1:
                          opt_len = *opt++;
                          memcpy(&numeric, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", renew-time=%d", ntohl(numeric));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_T2:
                          opt_len = *opt++;
                          memcpy(&numeric, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", rebind-time=%d", ntohl(numeric));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_SERVER_ID:
                          opt_len = *opt++;
                          memcpy(&ipaddr, opt, 4);
                          sim_debug(DEBUG_DHCP, &imp_dev, ", server-ip=%s", ipv4_inet_ntoa(ipaddr));
                          opt += opt_len;
                          break;
                    case DHCP_OPTION_MESSAGE_TYPE:
                          opt_len = *opt++;
                          sim_debug(DEBUG_DHCP, &imp_dev, "MessageType=%s", opr_names[*opt]);
                          opt += opt_len;
                          break;
                        }
                    }
                sim_debug(DEBUG_DHCP, &imp_dev, "\n");
            } else {
                if (udp->len && (imp_dev.dctrl & DEBUG_UDP))
                    sim_data_trace(&imp_dev, imp_unit, payload + sizeof(struct udp), "",
                                                       ntohs(udp->len), "", DEBUG_DATA);
                }
            break;
        case TCP_PROTO:
            tcp = (struct tcp *)payload;
            snprintf(src_port, sizeof(src_port), "%d", ntohs(tcp->tcp_sport));
            snprintf(dst_port, sizeof(dst_port), "%d", ntohs(tcp->tcp_dport));
            strlcpy(flags, "", sizeof(flags));
            for (flag=0; bits[flag].name; flag++) {
                if (ntohs(tcp->flags) & bits[flag].bitmask) {
                    if (*flags)
                        strlcat(flags, ",", sizeof(flags));
                    strlcat(flags, bits[flag].name, sizeof(flags));
                }

            }
            len = ntohs(ip->ip_len) - ((ip->ip_v_hl & 0xf) * 4 + (ntohs(tcp->flags) >> 12) * 4);
            if (len > 1500)
                len = 1500;
            sim_debug(DEBUG_TCP, &imp_dev, "%s %s%s %d byte packet from %s:%s to %s:%s\n", action,
                        flags, *flags ? ":" : "", (int)len, src_ip, src_port, dst_ip, dst_port);
            if (len && (imp_dev.dctrl & DEBUG_TCP))
                sim_data_trace(&imp_dev, imp_unit, payload + 4 * (ntohs(tcp->flags) >> 12), "", len, "", DEBUG_DATA);
            break;
        case ICMP_PROTO:
            icmp = (struct icmp *)payload;
            len = ntohs(ip->ip_len) - (ip->ip_v_hl & 0xf) * 4;
            sim_debug(DEBUG_ICMP, &imp_dev, "%s %s %d byte packet from %s to %s\n", action,
                (icmp->type < sizeof(icmp_types)/sizeof(icmp_types[0])) ? icmp_types[icmp->type] : "", (int)len, src_ip, dst_ip);
            if (len && (imp_dev.dctrl & DEBUG_ICMP))
                sim_data_trace(&imp_dev, imp_unit, payload + sizeof(struct icmp), "", len, "", DEBUG_DATA);
            break;
    }
}

void imp_write(struct imp_device *imp, ETH_PACK *packet) {
    imp_packet_debug(imp, "Sending", packet);
    eth_write(&imp->etherface, packet, NULL);
}

/*
 * Update the ARP table, first use free entry, else use oldest.
 */
void
imp_arp_update(struct imp_device *imp, in_addr_T ipaddr, ETH_MAC ethaddr, int age)
{
    struct arp_entry  *tabptr;
    int                i;
    char               mac_buf[20];

    /* Check if entry already in the table. */
    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        tabptr = &imp->arp_table[i];

        if (tabptr->ipaddr != 0) {
            if (tabptr->ipaddr == ipaddr) {
                if (0 != eth_mac_cmp(tabptr->ethaddr, ethaddr)) {
                    eth_copy_mac(tabptr->ethaddr, ethaddr);
                    eth_mac_fmt(ethaddr, mac_buf);
                    sim_debug(DEBUG_ARP, &imp_dev,
                              "updating entry for IP %s to %s\n",
                              ipv4_inet_ntoa(*((struct in_addr *)&ipaddr)), mac_buf);
                    }
                if (tabptr->age != ARP_DONT_AGE)
                    tabptr->age = age;
                return;
            }
        }
     }

    /* See if we can find an unused entry. */
    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        tabptr = &imp->arp_table[i];

        if (tabptr->ipaddr == 0)
            break;
    }

    /* If no empty entry search for oldest one. */
    if (tabptr->ipaddr != 0) {
        int       fnd = 0;
        int16     tmpage = 0;
        for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
            tabptr = &imp->arp_table[i];
            if (tabptr->age > tmpage) {
                tmpage = tabptr->age;
                fnd = i;
            }
        }
        tabptr = &imp->arp_table[fnd];
    }

    /* Now save the entry */
    eth_copy_mac(tabptr->ethaddr, ethaddr);
    tabptr->ipaddr = ipaddr;
    tabptr->age = age;
    eth_mac_fmt(ethaddr, mac_buf);
    sim_debug(DEBUG_ARP, &imp_dev,
              "creating entry for IP %s to %s, initial age=%d\n",
              ipv4_inet_ntoa(*((struct in_addr *)&ipaddr)), mac_buf, age);
}

/*
 * Age arp entries and free up old ones.
 */
void imp_arp_age(struct imp_device *imp)
{
    struct arp_entry  *tabptr;
    int                i;

    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        tabptr = &imp->arp_table[i];
        if (tabptr->ipaddr != 0) {      /* active entry? */
            if (tabptr->age != ARP_DONT_AGE)
                tabptr->age++;          /* Age it */
            /* expire too old entries */
            if (tabptr->age > IMP_ARP_MAX_AGE) {
                char mac_buf[20];

                eth_mac_fmt(tabptr->ethaddr, mac_buf);
                sim_debug(DEBUG_ARP, &imp_dev,
                          "discarding ARP entry for IP %s %s after %d seconds\n",
                          ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)), mac_buf, IMP_ARP_MAX_AGE);
                memset(tabptr, 0, sizeof(*tabptr));
            }
        }
    }
}


/*
 *  Process incomming ARP packet.
 */

void
imp_arp_arpin(struct imp_device *imp, ETH_PACK *packet)
{
    struct arp_hdr *arp;
    struct in_addr in_addr;
    int             op;
    char            mac_buf[20];

    /* Ignore packet if too short */
    if (packet->len < sizeof(struct arp_hdr))
       return;
    arp = (struct arp_hdr *)(&packet->msg[0]);
    op = ntohs(arp->opcode);
    imp_arp_update(imp, arp->sipaddr, arp->shwaddr, 0);

    switch (op) {
    case ARP_REQUEST:
        if (arp->dipaddr == imp->ip) {

           arp->opcode = htons(ARP_REPLY);
           memcpy(&arp->dhwaddr, &arp->shwaddr, 6);
           memcpy(&arp->shwaddr, &imp->mac, 6);
           memcpy(&arp->ethhdr.src, &imp->mac, 6);
           memcpy(&arp->ethhdr.dest, &arp->dhwaddr, 6);

           arp->dipaddr = arp->sipaddr;
           arp->sipaddr = imp->ip;
           arp->ethhdr.type = htons(ETHTYPE_ARP);
           packet->len = sizeof(struct arp_hdr);
           imp_write(imp, packet);
           eth_mac_fmt(arp->dhwaddr, mac_buf);
           sim_debug(DEBUG_ARP, &imp_dev, "replied to received request for IP %s from %s\n",
               ipv4_inet_ntoa(*((struct in_addr *)&imp->ip)), mac_buf);
         }
         break;

    case ARP_REPLY:
        /* Check if this is our address */
        if (arp->dipaddr == imp->ip) {
            struct imp_packet  *nq = NULL;                /* New send queue */

            eth_mac_fmt(arp->shwaddr, mac_buf);
            memcpy(&in_addr, &arp->sipaddr, sizeof(in_addr));
            sim_debug(DEBUG_ARP, &imp_dev, "received reply for IP %s as %s\n",
               ipv4_inet_ntoa(in_addr), mac_buf);
            /* Scan send queue, and send all packets for this host */
            while (imp->sendq != NULL) {
                struct imp_packet *temp = imp->sendq;
                imp->sendq = temp->next;

                if (temp->dest == arp->sipaddr) {
                    struct ip_hdr     *pkt = (struct ip_hdr *)
                                                     (&temp->packet.msg[0]);
                    memcpy(&pkt->ethhdr.dest, &arp->shwaddr, 6);
                    memcpy(&pkt->ethhdr.src, &imp->mac, 6);
                    pkt->ethhdr.type = htons(ETHTYPE_IP);
                    imp_write(imp, &temp->packet);
                    imp->rfnm_count++;
                    imp_free_packet(imp, temp);
                } else {
                    temp->next = nq;
                    nq = temp;
                }
            }
            imp->sendq = nq;
        }
        break;
    }
    return;
}

/*
 *  Send a ARP_REQUEST packet.
 */

void
imp_arp_arpout(struct imp_device *imp, in_addr_T ipaddr)
{
    struct arp_hdr    *arp;
    ETH_PACK           arp_pkt;

    memset(&arp_pkt, 0, sizeof(ETH_PACK));
    arp = (struct arp_hdr *)(&arp_pkt.msg[0]);
    memcpy(&arp->ethhdr.dest, &broadcast_ethaddr, 6);
    memcpy(&arp->ethhdr.src, &imp->mac, 6);
    arp->ethhdr.type = htons(ETHTYPE_ARP);
    memset(&arp->dhwaddr, 0x00, 6);
    memcpy(&arp->shwaddr, &imp->mac, 6);
    arp->dipaddr = ipaddr;
    arp->sipaddr = imp->ip;
    arp->opcode = htons(ARP_REQUEST);
    arp->hwtype = htons(ARP_HWTYPE_ETH);
    arp->protocol = htons(ETHTYPE_IP);
    arp->hwlen = 6;
    arp->protolen = 4;

    arp_pkt.len = sizeof(struct arp_hdr);
    imp_write(imp, &arp_pkt);
    sim_debug(DEBUG_ARP, &imp_dev, "sending request for IP %s\n", ipv4_inet_ntoa(*((struct in_addr *)&ipaddr)));
}

/*
 *  Lookup an IP's ARP entry.
 */

struct arp_entry *imp_arp_lookup(struct imp_device *imp, in_addr_T ipaddr)
{
    struct arp_entry  *tabptr;
    int                i;

    /* Check if entry already in the table. */
    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        tabptr = &imp->arp_table[i];

        if (tabptr->ipaddr != 0) {
            if (tabptr->ipaddr == ipaddr)
                return tabptr;
        }
    }
    return NULL;
}

t_stat imp_set_arp (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
    char abuf[CBUFSIZE];
    in_addr_T ip;
    ETH_MAC mac_addr;

    if (!cptr) return SCPE_IERR;

    cptr = get_glyph (cptr, abuf, '=');
    if (cptr && *cptr) {
        if (SCPE_OK != eth_mac_scan (mac_addr, cptr))  /* scan string for mac, put in mac */
            return sim_messagef(SCPE_ARG, "Invalid MAC address: %s\n", abuf);
    } else
        return sim_messagef(SCPE_ARG, "MAC address empty\n");
    if (ipv4_inet_aton (abuf, (struct in_addr *)&ip)) {
        imp_arp_update(&imp_data, ip, mac_addr, ARP_DONT_AGE);
        return SCPE_OK;
    }
    return sim_messagef(SCPE_ARG, "Invalid IP Address: %s\n", abuf);
}

t_stat imp_show_arp (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    struct arp_entry  *tabptr;
    int                i;

    fprintf (st, "%-17s%-19s%s\n", "IP Address:", "MAC:", "Age:");

    for (i = 0; i < IMP_ARPTAB_SIZE; i++) {
        char buf[32];

        tabptr = &imp_data.arp_table[i];

        if (tabptr->ipaddr == 0)
            continue;

        eth_mac_fmt(tabptr->ethaddr, buf);      /* format ethernet mac address */
        if (tabptr->age == ARP_DONT_AGE)
            fprintf (st, "%-17s%-19s%s\n",
                          ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)),
                          buf, "static");
        else
            fprintf (st, "%-17s%-19s%d\n",
                          ipv4_inet_ntoa(*((struct in_addr *)&tabptr->ipaddr)),
                          buf, tabptr->age);
        }
    return SCPE_OK;
}


static int sent_flag = 0;
void sent(int status) {
    sent_flag = 1;
}

static const char  *dhcp_state_names[] = {
                    "OFF",          "REQUESTING",   "INIT",         "REBOOTING",
                    "REBINDING",    "RENEWING",     "SELECTING",    "INFORMING",
                    "CHECKING",     "PERMANENT",    "BOUND",        "RELEASING",
                    "BACKING_OFF"
                    };

static const char  *dhcp_opr_names[16] = {
                    "",             "DISCOVER",     "OFFER",        "REQUEST",
                    "DECLINE",      "ACK",          "NAK",          "RELEASE",
                    "INFORM"
                    };

/* Send out a DHCP packet, fill in IP and Ethernet data. */
void
imp_do_send_dhcp(struct imp_device *imp,
                 const char *op,
                 ETH_PACK *packet,
                 uint8 *last,
                  const struct arp_entry *destarp)
{
    struct ip_hdr     *pkt;
    struct udp        *udp;
    struct udp_hdr     udp_hdr;
    struct in_addr     in_addr;
    int                len;
    char               mac_buf[20];

    pkt = (struct ip_hdr *)(&packet->msg[0]);
    udp = (struct udp *)(&packet->msg[sizeof(struct imp_eth_hdr) +
                          sizeof(struct ip)]);
    len = last - (uint8 *)udp;
    /* Fill in ethernet and IP packet */
    if (destarp)
        memcpy(&pkt->ethhdr.dest, &destarp->ethaddr, 6);
    else
        memcpy(&pkt->ethhdr.dest, &broadcast_ethaddr, 6);
    memcpy(&pkt->ethhdr.src, &imp->mac, 6);
    pkt->ethhdr.type = htons(ETHTYPE_IP);
    pkt->iphdr.ip_v_hl = 0x45;
    pkt->iphdr.ip_id = 1;
    pkt->iphdr.ip_ttl = 128;
    pkt->iphdr.ip_p = UDP_PROTO;
    pkt->iphdr.ip_dst = broadcast_ipaddr;
    udp->udp_sport = htons(DHCP_UDP_PORT_CLIENT);
    udp->udp_dport = htons(DHCP_UDP_PORT_SERVER);
    udp->len = htons(len);
    pkt->iphdr.ip_len = htons(len + sizeof(struct ip));
    ip_checksum((uint8 *)(&pkt->iphdr.ip_sum), (uint8 *)&pkt->iphdr, 20);
    ip_checksum((uint8 *)(&udp->chksum), (uint8 *)(udp), len);
    /* Compute checksum of psuedo header and data */
    udp_hdr.ip_src = pkt->iphdr.ip_src;
    udp_hdr.ip_dst = pkt->iphdr.ip_dst;
    udp_hdr.zero = 0;
    udp_hdr.proto = UDP_PROTO;
    udp_hdr.hlen = udp->len;
    checksumadjust((uint8 *)&udp->chksum, (uint8 *)(&udp_hdr), 0,
               (uint8 *)(&udp_hdr), sizeof(udp_hdr));
    packet->len = len + sizeof(struct ip_hdr);
    imp_write(imp, packet);

    eth_mac_fmt (pkt->ethhdr.dest, mac_buf);
    memcpy(&in_addr, &udp_hdr.ip_dst, sizeof(in_addr));
    sim_debug(DEBUG_DHCP, &imp_dev,
        "client sent %s packet to: %s:%d(%s)\n",
        op, ipv4_inet_ntoa(in_addr), ntohs(udp->udp_dport), mac_buf);
}

/* Handle incoming DCHP offer and other requests */
void
imp_do_dhcp_client(struct imp_device *imp, ETH_PACK *read_buffer)
{
    struct ip           *ip_hdr = (struct ip *)
                           (&read_buffer->msg[sizeof(struct imp_eth_hdr)]);
    struct imp_eth_hdr  *eth = (struct imp_eth_hdr  *)read_buffer->msg;
    int                 hl = (ip_hdr->ip_v_hl & 0xf) * 4;
    struct dhcp         *dhcp;
    struct udp          *upkt;
    struct udp_hdr      udp_hdr;
    struct in_addr      in_addr;
    uint8               *opt;
    uint16              sum;
    int                 len;
    in_addr_T           my_ip = 0;               /* Local IP address */
    in_addr_T           my_mask = 0;             /* Local IP mask */
    in_addr_T           my_gw = 0;               /* Gateway IP address */
    int                 lease_time = 0;          /* Lease time */
    int                 renew_time = 0;          /* Renewal time */
    int                 rebind_time = 0;         /* Rebind time */
    in_addr_T           dhcpip = 0;              /* DHCP server address */
    int                 opr = -1;                /* DHCP operation */
    char                mac_buf[20];             /* Source MAC address */

    upkt = (struct udp *)(&((uint8 *)(ip_hdr))[hl]);
    dhcp = (struct dhcp *)(&((uint8 *)(upkt))[sizeof(struct udp)]);

    ip_checksum((uint8 *)&sum, (uint8 *)ip_hdr, hl);
    if (sum != 0) {
       sim_printf("IP checksum error %x\r\n", sum);
       return;
    }
    ip_checksum((uint8 *)(&sum), (uint8 *)(upkt), ntohs(upkt->len));
    udp_hdr.ip_src = ip_hdr->ip_src;
    udp_hdr.ip_dst = ip_hdr->ip_dst;
    udp_hdr.zero = 0;
    udp_hdr.proto = UDP_PROTO;
    udp_hdr.hlen = upkt->len;
    checksumadjust((uint8 *)&sum, 0, 0, (uint8 *)(&udp_hdr), sizeof(udp_hdr));
    if (sum != 0) {
       sim_printf("UDP checksum error %x\r\n", sum);
       return;
    }

    if (memcmp(&dhcp->chaddr, &imp->mac, 6) != 0 || dhcp->xid != imp->dhcp_xid)
       return;

    if (dhcp->op != DHCP_BOOTREPLY)
       return;

    opt = &dhcp->options[0];

    my_ip = dhcp->yiaddr;

    /* Scan and collect the options we care about */
    while (*opt != DHCP_OPTION_END) {
        switch(*opt++) {
        case DHCP_OPTION_PAD:
                              break;
        default:
                              len = *opt++;
                              opt += len;
                              break;
        case DHCP_OPTION_SUBNET_MASK:
                              len = *opt++;
                              memcpy(&my_mask, opt, 4);
                              opt += len;
                              break;
        case DHCP_OPTION_ROUTER:
                              len = *opt++;
                              memcpy(&my_gw, opt, 4);
                              opt += len;
                              break;
        case DHCP_OPTION_REQUESTED_IP:
                              len = *opt++;
                              memcpy(&my_ip, opt, 4);
                              opt += len;
                              break;
        case DHCP_OPTION_LEASE_TIME:
                              len = *opt++;
                              memcpy(&lease_time, opt, 4);
                              lease_time = ntohl(lease_time);
                              opt += len;
                              break;
        case DHCP_OPTION_T1:
                              len = *opt++;
                              memcpy(&renew_time, opt, 4);
                              renew_time = ntohl(renew_time);
                              opt += len;
                              break;
        case DHCP_OPTION_T2:
                              len = *opt++;
                              memcpy(&rebind_time, opt, 4);
                              rebind_time = ntohl(rebind_time);
                              opt += len;
                              break;
        case DHCP_OPTION_SERVER_ID:
                              len = *opt++;
                              memcpy(&dhcpip, opt, 4);
                              opt += len;
                              break;
        case DHCP_OPTION_MESSAGE_TYPE:
                              len = *opt++;
                              opr = *opt;
                              opt += len;
                              break;
        }
    }

    eth_mac_fmt(eth->src, mac_buf);
    memcpy(&in_addr, &udp_hdr.ip_src, sizeof(in_addr));
    sim_debug(DEBUG_DHCP, &imp_dev,
        "client incoming %s packet: dhcp_state=%s - wait_time=%d from: %s:%d(%s)\n",
                   (opr == -1) ? "" : dhcp_opr_names[opr],
                    dhcp_state_names[imp->dhcp_state], imp->dhcp_wait_time,
                    ipv4_inet_ntoa(in_addr), ntohs(upkt->udp_sport), mac_buf);

    /* Process an offer message */
    if (opr == DHCP_OFFER && imp->dhcp_state == DHCP_STATE_SELECTING) {
        /* Create a REQUEST Packet with IP address given */
        imp->dhcp_state = DHCP_STATE_REQUESTING;
        imp->ip = my_ip;
        imp->dhcp_wait_time = 0;
        imp->dhcp_retry_after = 4;
        imp_dhcp_request(imp, dhcpip);
    }
    if (opr == DHCP_ACK && imp->dhcp_xid == dhcp->xid &&
             (imp->dhcp_state == DHCP_STATE_REQUESTING ||
              imp->dhcp_state == DHCP_STATE_REBINDING ||
              imp->dhcp_state == DHCP_STATE_RENEWING)) {
        ETH_PACK            arp_pkt;
        struct arp_hdr      *arp;

        /* Set my IP address to one offered */
        imp->ip = dhcp->yiaddr;
        imp->ip_mask = my_mask;
        imp->gwip = my_gw;
        imp->dhcpip = dhcpip;
        imp->dhcp_state = DHCP_STATE_BOUND;
        imp->dhcp_lease = lease_time;
        imp->dhcp_renew = imp->dhcp_lease / 2;
        if (renew_time)
            imp->dhcp_renew = renew_time;
        imp->dhcp_rebind = (7 * imp->dhcp_lease) / 8;
        if (rebind_time)
            imp->dhcp_rebind = rebind_time;
        for (len = 0; len < 33; len++) {
            if (htonl(mask[len]) == my_mask) {
                imp->maskbits = 32 - len;
                break;
            }
        /* Record a static ARP for the DHCP server */
        imp_arp_update(imp, dhcpip, eth->src, ARP_DONT_AGE);

        /* Broadcast an ARP Reply with the assigned IP Address */
        memset(&arp_pkt, 0, sizeof(ETH_PACK));
        arp = (struct arp_hdr *)(&arp_pkt.msg[0]);
        memcpy(&arp->ethhdr.dest, &broadcast_ethaddr, 6);
        memcpy(&arp->ethhdr.src, &imp->mac, 6);
        arp->ethhdr.type = htons(ETHTYPE_ARP);
        memcpy(&arp->dhwaddr, &imp->mac, 6);
        memcpy(&arp->shwaddr, &imp->mac, 6);
        arp->dipaddr = imp->ip;
        arp->sipaddr = imp->ip;
        arp->opcode = htons(ARP_REPLY);
        arp->hwtype = htons(ARP_HWTYPE_ETH);
        arp->protocol = htons(ETHTYPE_IP);
        arp->hwlen = 6;
        arp->protolen = 4;
        arp_pkt.len = sizeof(struct arp_hdr);
        imp_write(imp, &arp_pkt);
        }
    }
    if (opr == DHCP_NAK && (imp->dhcp_state == DHCP_STATE_REQUESTING ||
              imp->dhcp_state == DHCP_STATE_REBINDING ||
              imp->dhcp_state == DHCP_STATE_RENEWING)) {
        imp->ip = imp->gwip = imp->dhcpip = imp->maskbits = 0;
        imp->dhcp_state = DHCP_STATE_OFF;

    }
}

void
imp_dhcp_timer(struct imp_device *imp)
{
    if (!(imp_unit[0].flags & UNIT_DHCP))
        return;

    if (imp->dhcp_state == DHCP_STATE_BOUND)
        sim_debug(DEBUG_DETAIL, &imp_dev,
            "DHCP timer: dhcp_state=BOUND, Lease remaining time=%d\n", imp->dhcp_lease);
    else
        sim_debug(DEBUG_DETAIL, &imp_dev,
            "DHCP timer: dhcp_state=%s, wait_time=%d\n", dhcp_state_names[imp->dhcp_state],
                      imp->dhcp_wait_time);

    if ((imp->dhcp_state == DHCP_STATE_BOUND) ||
        (imp->dhcp_state == DHCP_STATE_REBINDING) ||
        (imp->dhcp_state == DHCP_STATE_RENEWING)) {
        if (imp->dhcp_lease != DHCP_INFINITE_LEASE) {
            if (imp->dhcp_lease-- == 0) {
                imp->dhcp_state = DHCP_STATE_OFF;
            } else {
                if (imp->dhcp_rebind-- == 0) {
                    imp->dhcp_state = DHCP_STATE_REBINDING;
                    imp->dhcpip = 0;
                    imp->dhcp_wait_time = 0;
                    imp->dhcp_retry_after = 4;
                } else {
                    if (imp->dhcp_renew-- == 0) {
                        imp->dhcp_state = DHCP_STATE_RENEWING;
                        imp->dhcp_wait_time = 0;
                        imp->dhcp_retry_after = 4;
                    }
                }
            }
        }
    }

    switch (imp->dhcp_state) {
    case DHCP_STATE_REBINDING:
    case DHCP_STATE_RENEWING:
         /* Create a REQUEST Packet with IP address previously given */
         imp_dhcp_request(imp, (imp->dhcp_state == DHCP_STATE_RENEWING) ? imp->dhcpip : 0);
         break;

    case DHCP_STATE_OFF:
         imp->dhcp_wait_time = 0;
         imp->dhcp_retry_after = 4;
         imp_dhcp_discover(imp);
         break;

    case DHCP_STATE_SELECTING:
    case DHCP_STATE_REQUESTING:
         imp->dhcp_wait_time++;
         if (imp->dhcp_wait_time % imp->dhcp_retry_after == 0) {
             if (imp->dhcp_retry_after < 64)
                 imp->dhcp_retry_after *= 2;
             imp->dhcp_wait_time = 0;
             if (imp->dhcp_state == DHCP_STATE_SELECTING)
                 imp_dhcp_discover(imp);
             else
                 imp_dhcp_request(imp, imp->dhcpip);
         }
         break;

    default:
         break;
    }
}

void
imp_dhcp_discover(struct imp_device *imp)
{
    ETH_PACK          dhcp_pkt;
    struct dhcp       *dhcp;
    uint8             *opt;
    int               len;

    /* Fill in Discover packet */
    memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE);
    dhcp = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) +
                          sizeof(struct ip) + sizeof(struct udp)]);

    dhcp->op = DHCP_BOOTREQUEST;
    dhcp->htype = DHCP_HTYPE_ETH;
    dhcp->hlen = 6;
    dhcp->xid = ++imp->dhcp_xid;
    dhcp->secs = imp->dhcp_wait_time;
    dhcp->cookie = htonl(DHCP_MAGIC_COOKIE);
    memcpy(&dhcp->chaddr, &imp->mac, 6);
    opt = &dhcp->options[0];
    *opt++ = DHCP_OPTION_MESSAGE_TYPE;
    *opt++ = 1;
    *opt++ = DHCP_DISCOVER;
    *opt++ = DHCP_OPTION_CLIENT_ID;
    *opt++ = 7;
    *opt++ = DHCP_HTYPE_ETH;
    for (len = 0; len < 6; len++)
        *opt++ = imp->mac[len];
    if (imp->ip != 0) {
        *opt++ = DHCP_OPTION_REQUESTED_IP;
        *opt++ = 0x04;
        memcpy(opt, &imp->ip, 4);
        opt += 4;
    }
    *opt++= DHCP_OPTION_PARAMETER_REQUEST_LIST;
    *opt++= 2;     /* Number */
    *opt++= DHCP_OPTION_SUBNET_MASK;     /* Subnet mask */
    *opt++= DHCP_OPTION_ROUTER;         /* Routers */
    *opt++= DHCP_OPTION_END;  /* Last option */
    /* Fill in ethernet and IP packet */
    imp_do_send_dhcp(imp, "DISCOVER", &dhcp_pkt, opt, NULL);
    imp->dhcp_state = DHCP_STATE_SELECTING;
}

void
imp_dhcp_request(struct imp_device *imp, in_addr_T dhcpip)
{
    ETH_PACK          dhcp_pkt;
    struct dhcp       *dhcp;
    uint8             *opt;
    int               len;

    /* Fill in Request packet */
    memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE);
    dhcp = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) +
                          sizeof(struct ip) + sizeof(struct udp)]);

    dhcp->op = DHCP_BOOTREQUEST;
    dhcp->htype = DHCP_HTYPE_ETH;
    dhcp->hlen = 6;
    dhcp->xid = imp->dhcp_xid;
    dhcp->cookie = htonl(DHCP_MAGIC_COOKIE);
    memcpy(&dhcp->chaddr, &imp->mac, 6);
    opt = &dhcp->options[0];
    *opt++ = DHCP_OPTION_MESSAGE_TYPE;
    *opt++ = 1;
    *opt++ = DHCP_REQUEST;
    *opt++ = DHCP_OPTION_REQUESTED_IP;
    *opt++ = 4;
    memcpy(opt, &imp->ip, 4);
    opt += 4;
    if (dhcpip) {
        *opt++ = DHCP_OPTION_SERVER_ID;
        *opt++ = 4;
        memcpy(opt, &dhcpip, 4);
        opt += 4;
    }
    *opt++ = DHCP_OPTION_CLIENT_ID;
    *opt++ = 7;
    *opt++ = DHCP_HTYPE_ETH;
    for (len = 0; len < 6; len++)
        *opt++ = imp->mac[len];
    *opt++ = DHCP_OPTION_PARAMETER_REQUEST_LIST;
    *opt++ = 4;     /* Number */
    *opt++ = DHCP_OPTION_SUBNET_MASK;     /* Subnet mask */
    *opt++ = DHCP_OPTION_ROUTER;         /* Routers */
    *opt++ = DHCP_OPTION_T1;            /* Renewal time */
    *opt++ = DHCP_OPTION_T2;            /* Rebinding time */
    *opt++ = DHCP_OPTION_END;  /* Last option */
    imp_do_send_dhcp(imp, "REQUEST", &dhcp_pkt, opt, imp_arp_lookup(imp, dhcpip));
}

void
imp_dhcp_release(struct imp_device *imp)
{
    ETH_PACK          dhcp_pkt;
    struct dhcp       *dhcp;
    uint8             *opt;
    int               len;


    /* Nothing to send if we are not bound */
    if (imp->dhcp_state == DHCP_STATE_OFF)
        return;
    /* Fill in DHCP RELEASE PACKET */
    memset(&dhcp_pkt.msg[0], 0, ETH_FRAME_SIZE);
    dhcp = (struct dhcp *)(&dhcp_pkt.msg[sizeof(struct imp_eth_hdr) +
                          sizeof(struct ip) + sizeof(struct udp)]);

    dhcp->op = DHCP_BOOTREQUEST;
    dhcp->htype = DHCP_HTYPE_ETH;
    dhcp->hlen = 6;
    dhcp->xid = ++imp->dhcp_xid;
    dhcp->ciaddr = imp->ip;
    dhcp->cookie = htonl(DHCP_MAGIC_COOKIE);
    memcpy(&dhcp->chaddr, &imp->mac, 6);
    opt = &dhcp->options[0];
    *opt++ = DHCP_OPTION_SERVER_ID;
    *opt++ = 4;
    memcpy(opt, &imp->dhcpip, 4);
    opt += 4;
    *opt++ = DHCP_OPTION_MESSAGE_TYPE;
    *opt++ = 1;
    *opt++ = DHCP_RELEASE;
    if (imp->ip != 0) {
        *opt++ = DHCP_OPTION_REQUESTED_IP;
        *opt++ = 0x04;
        memcpy(opt, &imp->ip, 4);
        opt += 4;
    }
    *opt++ = DHCP_OPTION_CLIENT_ID;
    *opt++ = 7;
    *opt++ = DHCP_HTYPE_ETH;
    for (len = 0; len < 6; len++)
        *opt++ = imp->mac[len];
    *opt++= DHCP_OPTION_END;  /* Last option */
    imp_do_send_dhcp(imp, "RELEASE", &dhcp_pkt, opt, imp_arp_lookup(imp, imp->dhcpip));
    imp->dhcp_state = DHCP_STATE_OFF;
    imp->ip = imp->gwip = imp->dhcpip = imp->maskbits = 0;
}



static char *
ipv4_inet_ntoa(struct in_addr ip)
{
   static char str[20];

   if (sim_end)
       sprintf (str, "%d.%d.%d.%d", ip.s_addr & 0xFF, (ip.s_addr >> 8) & 0xFF,
                              (ip.s_addr >> 16) & 0xFF, (ip.s_addr >> 24) & 0xFF);
   else
       sprintf (str, "%d.%d.%d.%d", (ip.s_addr >> 24) & 0xFF, (ip.s_addr >> 16) & 0xFF,
                              (ip.s_addr >> 8) & 0xFF, ip.s_addr & 0xFF);
   return str;
}

static
int ipv4_inet_aton(const char *str, struct in_addr *inp)
{
    unsigned long bytes[4];
    int i = 0;
    char *end;
    in_addr_T val;

    for (i=0; (i < 4) && isdigit (*str); i++) {
        bytes[i] = strtoul (str, &end, 0);
        if (str == end)
            return 0;
        str = end;
        if (*str == '.')
            ++str;
        }
    if (*str && (*str != '/'))
        return 0;
    switch (i) {
    case 1:
            val = bytes[0];
            break;
    case 2:
            if ((bytes[0] > 0xFF) || (bytes[1] > 0xFFFFFF))
                return 0;
            val = (bytes[0] << 24) | bytes[1];
            break;
    case 3:
            if ((bytes[0] > 0xFF) || (bytes[1] > 0xFF) || (bytes[2] > 0xFFFF))
                return 0;
            val = (bytes[0] << 24) | (bytes[1] << 16) | bytes[2];
            break;
    case 4:
            if ((bytes[0] > 0xFF) || (bytes[1] > 0xFF) || (bytes[2] > 0xFF)
                  || (bytes[3] > 0xFF))
                return 0;
            val = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
            break;
    default:
            return 0;
    }
    if (inp)
        *(in_addr_T *)inp = htonl (val);
    return 1;
}

#if MPX_DEV
t_stat imp_set_mpx (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    int32 mpx;
    t_stat r;

    if (cptr == NULL)
        return SCPE_ARG;
    mpx = (int32) get_uint (cptr, 8, 8, &r);
    if (r != SCPE_OK)
        return r;
    imp_mpx_lvl = mpx;
    return SCPE_OK;
}

t_stat imp_show_mpx (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
   if (uptr == NULL)
      return SCPE_IERR;

   fprintf (st, "MPX=%o", imp_mpx_lvl);
   return SCPE_OK;
}
#endif

t_stat imp_show_mac (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
    char buffer[20];
    eth_mac_fmt(imp_data.mac, buffer);
    fprintf(st, "MAC=%s", buffer);
    return SCPE_OK;
}

t_stat imp_set_mac (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
    t_stat status;

    if (!cptr) return SCPE_IERR;
    if (uptr->flags & UNIT_ATT) return SCPE_ALATT;

    status = eth_mac_scan_ex(imp_data.mac, cptr, uptr);
    if (status != SCPE_OK)
      return status;

    memcpy(&imp_data.bcast, &broadcast_ethaddr, 6);
    return SCPE_OK;
}

t_stat imp_show_ip (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
   struct in_addr ip;
   ip.s_addr = imp_data.ip;
   fprintf (st, "IP=%s/%d", ipv4_inet_ntoa(ip), imp_data.maskbits);
   return SCPE_OK;
}

t_stat imp_set_ip (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
    char abuf[CBUFSIZE];
    struct in_addr  ip;

    if (!cptr) return SCPE_IERR;
    if (uptr->flags & UNIT_ATT) return SCPE_ALATT;

    cptr = get_glyph (cptr, abuf, '/');
    if (cptr && *cptr) {
          imp_data.maskbits = atoi (cptr);
          if (imp_data.maskbits < 0)
              imp_data.maskbits = 0;
          if (imp_data.maskbits > 32)
              imp_data.maskbits = 32;
    } else
          imp_data.maskbits = 32;
    if (ipv4_inet_aton (abuf, &ip)) {
        imp_data.ip = ip.s_addr;
        imp_data.ip_mask =  htonl(mask[imp_data.maskbits]);
        if (uptr->flags & UNIT_DHCP) {
            uptr->flags &= ~UNIT_DHCP;
            return sim_messagef (SCPE_OK, "IMP DHCP disabled\n");
        }
        return SCPE_OK;
    }
    return SCPE_ARG;
}

t_stat imp_show_gwip (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
   struct in_addr ip;

   ip.s_addr = imp_data.gwip;
   fprintf (st, "GW=%s", ipv4_inet_ntoa(ip));
   return SCPE_OK;
}

t_stat imp_set_gwip (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
    struct in_addr  ip;
    if (!cptr) return SCPE_IERR;
    if (uptr->flags & UNIT_ATT) return SCPE_ALATT;

    if (ipv4_inet_aton (cptr, &ip)) {
        imp_data.gwip = ip.s_addr;
        if (uptr->flags & UNIT_DHCP) {
            uptr->flags &= ~UNIT_DHCP;
            return sim_messagef (SCPE_OK, "IMP DHCP disabled\n");
        }
       return SCPE_OK;
    }
    return SCPE_ARG;
}

t_stat imp_show_dhcpip (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    if (!(uptr->flags & UNIT_DHCP)) {
        fprintf (st, "DHCP disabled");
    } else {
        struct in_addr ip;
        ip.s_addr = imp_data.dhcpip;
        fprintf (st, "DHCP Server IP=%s", ipv4_inet_ntoa(ip));
        if (imp_data.dhcp_state == DHCP_STATE_BOUND) {
            fprintf (st, ", Lease Expires in %d seconds", imp_data.dhcp_lease);
        } else {
            fprintf (st, ", State:%s, Waited %d seconds", dhcp_state_names[imp_data.dhcp_state],
                  imp_data.dhcp_wait_time);
        }
    }
   return SCPE_OK;
}

t_stat imp_show_hostip (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
   struct in_addr ip;

   ip.s_addr = imp_data.hostip;
   fprintf (st, "HOST=%s", ipv4_inet_ntoa(ip));
   return SCPE_OK;
}

t_stat imp_set_hostip (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
   struct in_addr ip;
    if (!cptr) return SCPE_IERR;
    if (uptr->flags & UNIT_ATT) return SCPE_ALATT;

    if (ipv4_inet_aton (cptr, &ip)) {
       imp_data.hostip = ip.s_addr;
       return SCPE_OK;
    }
    return SCPE_ARG;
}

struct imp_packet *
imp_get_packet(struct imp_device *imp) {
    struct imp_packet *ret;
    /* Check if list empty */
    if ((ret = imp->freeq) != NULL) {
        imp->freeq = ret->next;
        ret->next = NULL;
    }
    return ret;
}

void
imp_free_packet(struct imp_device *imp, struct imp_packet *p) {
    p->next = imp->freeq;
    imp->freeq = p;
}

t_stat imp_reset (DEVICE *dptr)
{
    int  i;
    struct imp_packet *p;

    for (i = 0; i < 6; i++) {
        if (imp_data.mac[i] != 0)
            break;
    }
    if (i == 6) {   /* First call to reset? */
        /* Set a default MAC address in a BBN assigned OID range no longer in use */
        imp_set_mac (dptr->units, 0, "00:00:02:00:00:00/24", NULL);
        /* Clear ARP table. */
        memset(imp_data.arp_table, 0, sizeof(imp_data.arp_table));
        imp_data.dhcp_state = DHCP_STATE_OFF;
    }
    /* Clear queues. */
    imp_data.sendq = NULL;
    /* Set up free queue */
    p = NULL;
    for (i = 0; i < (int)(sizeof(imp_buffer)/sizeof(struct imp_packet)); i++) {
        imp_buffer[i].next = p;
        p = &imp_buffer[i];
    }
    /* Fix last entry */
    imp_data.freeq = p;
    imp_data.init_state = 0;
    last_coni = sim_gtime();
#if KS
    imp_icsr = CSR_RDY;
    imp_ocsr = CSR_RDY;
#endif
    if (imp_unit[0].flags & UNIT_ATT)
        sim_activate_after(&imp_unit[2], 1000000);    /* Start Timer service */
    return SCPE_OK;
}

/* attach device: */
t_stat imp_attach(UNIT* uptr, CONST char* cptr)
{
    t_stat status;
    char* tptr;
    char buf[32];

#if !KS
    /* Set to correct device number */
    switch(GET_DTYPE(imp_unit[0].flags)) {
    case TYPE_MIT:
                   imp_dib.dev_num = IMP_DEVNUM;
                   break;
    case TYPE_BBN:
                   imp_dib.dev_num = BBN_IMP_DEVNUM;
                   break;
    case TYPE_WAITS:
                   imp_dib.dev_num = WA_IMP_DEVNUM;
                   break;
    }
#endif
    if (!(uptr->flags & UNIT_DHCP) && imp_data.ip == 0)
      return sim_messagef (SCPE_NOATT, "%s: An IP Address must be specified when DHCP is disabled\n",
               imp_dev.name);

    tptr = (char *) malloc(strlen(cptr) + 1);
    if (tptr == NULL) return SCPE_MEM;
    strcpy(tptr, cptr);

    memset(&imp_data.ReadQ, 0, sizeof(ETH_QUE));
    status = eth_open(&imp_data.etherface, cptr, &imp_dev, DEBUG_ETHER);
    if (status != SCPE_OK) {
      free(tptr);
      return status;
    }
    eth_mac_fmt(imp_data.mac, buf);      /* format ethernet mac address */
    if (SCPE_OK != eth_check_address_conflict (&imp_data.etherface, imp_data.mac)) {
      eth_close(&imp_data.etherface);
      free(tptr);
      return sim_messagef (SCPE_NOATT, "%s: MAC Address Conflict on LAN for address %s\n",
                      imp_dev.name, buf);
    }
    if (SCPE_OK != eth_filter(&imp_data.etherface, 2, &imp_data.mac, 0, 0)) {
      eth_close(&imp_data.etherface);
      free(tptr);
      return sim_messagef (SCPE_NOATT, "%s: Can't set packet filter for MAC Address %s\n",
                       imp_dev.name, buf);
    }

    uptr->filename = tptr;
    uptr->flags |= UNIT_ATT;
    eth_setcrc(&imp_data.etherface, 0);     /* Don't need CRC */

    /* init read queue (first time only) */
    status = ethq_init(&imp_data.ReadQ, 8);
    if (status != SCPE_OK) {
      eth_close(&imp_data.etherface);
      uptr->filename = NULL;
      free(tptr);
      return sim_messagef (status, "%s: Can't initialize receive queue\n", imp_dev.name);
    }

    /* Pick a relatively unique starting xid, presuming that the
       MAC address has been verified unique on the LAN by eth_open */
    imp_data.dhcp_xid = (imp_data.mac[0] | (imp_data.mac[1] << 8) |
                        (imp_data.mac[2] << 16) | (imp_data.mac[3] << 24)) + (uint32)time(NULL);
    imp_data.dhcp_state = DHCP_STATE_OFF;
    memset(imp_data.arp_table, 0, sizeof(imp_data.arp_table));

    /* If we're not doing DHCP and a gateway is defined on the network
       then define a static APR entry for the gateway to facilitate
       outbound routing */
    if (!(uptr->flags & UNIT_DHCP) && imp_data.gwip) {
      ETH_PACK                read_buffer;
      struct imp_eth_hdr      *hdr = (struct imp_eth_hdr *)(&read_buffer.msg[0]);
      int                     type;
      struct arp_entry        *arp;

      imp_arp_arpout(&imp_data, imp_data.gwip);
      sim_os_ms_sleep (300);   /* wait for an ARP response to arrive */

      /* empty the read queue and find ARP_REPLYs */
      do {
        memset (&read_buffer, 0, sizeof(read_buffer));
        status = eth_read (&imp_data.etherface, &read_buffer, NULL);
        type = ntohs(hdr->type);
        if (type == ETHTYPE_ARP)
          imp_arp_arpin(&imp_data, &read_buffer);
      } while (read_buffer.len > 0);
      if ((arp = imp_arp_lookup(&imp_data, imp_data.gwip)))
        imp_arp_update(&imp_data, imp_data.gwip, arp->ethaddr, ARP_DONT_AGE);
    }

    eth_set_async (&imp_data.etherface, 0); /* Allow Asynchronous inbound packets */
    sim_activate_after(&imp_unit[2], 1000000);    /* Start Timer service */
    return SCPE_OK;
}

/* detach device: */

t_stat imp_detach(UNIT* uptr)
{

    if (uptr->flags & UNIT_ATT) {
        /* If DHCP, release our IP address */
        if (uptr->flags & UNIT_DHCP) {
          imp_dhcp_release(&imp_data);
        }
        sim_cancel (uptr+1);                /* stop the packet timing services */
        sim_cancel (uptr+2);                /* stop the clock timer services */
        eth_close (&imp_data.etherface);
        free(uptr->filename);
        uptr->filename = NULL;
        uptr->flags &= ~UNIT_ATT;
    }
    return SCPE_OK;
}

t_stat imp_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "IMP interface\n\n");
fprintf (st, "The IMP acted as an interface to the early internet. ");
fprintf (st, "This interface operated\nat the TCP/IP level rather than the ");
fprintf (st, "Ethernet level. This interface allows for\nITS or Tenex to be ");
fprintf (st, "placed on the internet. The interface connects up to a TAP\n");
fprintf (st, "or direct ethernet connection. If the host is to be run at an ");
fprintf (st, "arbitrary IP\naddress, then the HOST should be set to the IP ");
fprintf (st, "of ITS. The network interface\nwill translate this IP address ");
fprintf (st, "to the one set in IP. If HOST is set to 0.0.0.0,\nno ");
fprintf (st, "translation will take place. IP should be set to the external ");
fprintf (st, "address of\nthe IMP, along the number of bits in the net mask. ");
fprintf (st, "GW points to the default\nrouter. If DHCP is enabled these ");
fprintf (st, "will be set from DHCP when the IMP is attached.\nIf IP is set ");
fprintf (st, "and DHCP is enabled, when the IMP is attached it will inform\n");
fprintf (st, "the local DHCP server of it's address.\n\n");
fprint_set_help (st, dptr);
fprint_show_help (st, dptr);
eth_attach_help(st, dptr, uptr, flag, cptr);
return SCPE_OK;
}


const char *imp_description (DEVICE *dptr)
{
    return "KA Host/IMP interface";
}
#endif
