#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	Makefile
#	aarp.c
#	aarp.h
#	aarp_defs.h
#	bridge_desc
#	ddpport.c
#	ddpport.h
#	ddprouter.c
#	ddpsvcs.c
#	desc.ms
#	dlip.c
#	ethertalk.c
#	ethertalk.h
#	gw.h
#	hash.3
#	hash.c
#	hash.h
#	if_desc.h
#	kip_mpx.c
#	log.c
#	log.h
#	mpxddp.h
#	node.h
#	proto_intf.h
#	rtmp.c
#	snitp.c
#	uab.8
#	uab.c
#	other/abmkip.c
#	whatiswhat
# This archive created: Mon Oct 10 18:29:08 1988
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
UAB - Unix AppleTalk Bridge

AppleTalk Bridge For Unix

written by Charlie C. Kim
    Academic Computing and Communications Group
    Center For Computing Activities
    Columbia University
August 1988

Copyright (c) 1988 by The Trustees of Columbia University 
 in the City of New York.

Permission is granted to any individual or institution to use,
copy, or redistribute this software so long as it is not sold for
profit, provided that this notice and the original copyright
notices are retained.  Columbia University nor the author make no
representations about the suitability of this software for any
purpose.  It is provided "as is" without express or implied
warranty.

-----

See whatiswhat for a general description of modules.

See desc.ms for general overview.

See uab.8 for info on running uab.

SHAR_EOF
if test 811 -ne "`wc -c < 'README'`"
then
	echo shar: "error transmitting 'README'" '(should have been 811 characters)'
fi
chmod 440 'README'
fi
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
CFLAGS=-g -I. -DDEBUG
LFLAGS=-g
LIBS=-lcap
# for ultrix
POBJS=dlip.o
# for sunos40
#POBJS=snitp.o
SRCS=aarp.c kip_mpx.c rtmp.c ethertalk.c ddprouter.c ddpsvcs.c ddpport.c \
	hash.c log.c
OBJS=aarp.o kip_mpx.o rtmp.o ethertalk.o ddprouter.o ddpsvcs.o ddpport.o \
	hash.o log.o

uab:	uab.o	${OBJS} ${POBJS}
	cc ${LFLAGS} -o uab uab.o ${OBJS} ${POBJS} ${LIBS}

kip_mpx.o: kip_mpx.c mpxddp.h gw.h node.h ddpport.h
	cc -c ${CFLAGS} -DMTAB=\"/usr/local/lib/cap/etalk.local\" kip_mpx.c

uab.o: uab.c mpxddp.h gw.h node.h ddpport.h if_desc.h
	cc -c ${CFLAGS} -DUAB_PIDFILE=\"/usr/local/lib/cap/uab.pid\" uab.c

lint:
	lint -h uab.c ${SRCS}

clean:
	@rm -f *.o uab

# ddpport.h: mpxddp.h node.h
# gw.h: node.h ddport.h (mpxddp.h)
# if_desc.h: mpxddp.h

ddprouter.o: ddprouter.c gw.h node.h ddpport.h mpxddp.h
rtmp.o: rtmp.c gw.h node.h ddpport.h mpxddp.h

ethertalk.o: ethertalk.c proto_intf.h ethertalk.h node.h \
	ddpport.h log.h if_desc.h mpxddp.h
aarp.o: aarp.c proto_intf.h ethertalk.h log.h aarp_defs.h aarp.h

ddpport.o: ddpport.c log.h ddpport.h node.h mpxddp.h

log.o: log.c log.h

dlip.o: dlip.c proto_intf.h
snitp.o: snitp.c proto_intf.h

hash.o: hash.c hash.h

SHAR_EOF
if test 1164 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 1164 characters)'
fi
chmod 440 'Makefile'
fi
if test -f 'aarp.c'
then
	echo shar: "will not over-write existing file 'aarp.c'"
else
cat << \SHAR_EOF > 'aarp.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/16 17:12:10 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/aarp.c,v 1.26 88/09/16 17:12:10 cck Rel $";
static char revision[] = "$Revision: 1.26 $";

/*
 * aarp.c - AppleTalk Address Resolution Protocol handler
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  April 3, 1988  CCKim Created
 *  Aug 26, 1988 Moved into seperate module from ethertalk
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <hash.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <netat/appletalk.h>
#include "proto_intf.h"
#include "ethertalk.h"
#include "aarp_defs.h"
#include "aarp.h"		/* double checks aarp.h */
#include "log.h"

#define LOG_LOG 0
#define LOG_BASE 1
#define LOG_LOTS 9

private int aarptab_compare();
private u_int aarp_compress();
private caddr_t aarptab_new();
private caddr_t aarptab_init();
private AARP_ENTRY *aarptab_find();
private AARP_ENTRY *aarptab_get();
private int aarptab_delete();

export caddr_t aarp_init();
export int aarp_get_host_addr();
export int aarp_resolve();
export int aarp_insert();
export int aarp_acquire_etalk_node();
private aarp_listener();
private AARP_ENTRY *aarp_find_free_etalk_node();
private probe_and_request_driver();
private void aarp_probed();
private void aarp_reply();
private void aarp_request();
private struct ai_host_node *host_node();

/* pointers to host ethernet and broadcast addresess */
private u_char b_eaddr[EHRD] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

/* list of all the node ids: DUMP ONLY  */
private AARP_ENTRY *aarptab_node_list;

private AARP_ENTRY *aarptab_free_list;


private int m_aarptab = 0;
private int m_probe = 0;
private int f_probe = 0;


/* compare two ethertalk node addresses */
private int
aarptab_compare(k,node)
struct ethertalkaddr *k;
AARP_ENTRY *node;
{
  /* EtherTalk II fixup */
  return(k->node - node->aae_pa.node);
}

/* compress an ethertalk node addresses */
private u_int
aarptab_compress(k)
struct ethertalkaddr *k;
{
  /* EtherTalk II fixup */
  return(k->node);
}

/* create a new arp table entry based upon the ethertalk node */
private caddr_t
aarptab_new(k)
struct ethertalkaddr *k;
{
  AARP_ENTRY *en;

  if (aarptab_free_list) {
    en = aarptab_free_list;	/* get first node */
    aarptab_free_list = en->aae_next; /* and unlink from free list */
    /* if dump, don't need to link into dump table, already there */
  } else {
    if ((en = (AARP_ENTRY *)malloc(sizeof(AARP_ENTRY))) == NULL)
      return(NULL);
    m_aarptab++;
  }
  en->aae_next = aarptab_node_list;
  aarptab_node_list = en;
  en->aae_flags = 0;		/* clear flags */
  en->aae_used = 0;		/* clear used flag */
  en->aae_ttl = 0;		/* clear ttl */
  bcopy(k, &en->aae_pa, sizeof(*k)); /* copy in node address */
  return((caddr_t)en);
}

/* initial hash table for aarp table */
private caddr_t
aarptab_init()
{
  return(h_new(HASH_POLICY_CHAIN, HASH_TYPE_MULTIPLICATIVE,
			  AARP_TAB_SIZE,
			  aarptab_compare, aarptab_new, aarptab_compress, 
			  NULL, NULL, NULL));
}

/* see if we need to try to speed up hash lookups */
aarptab_speedup(aih)
AI_HANDLE *aih;
{
  struct hash_statistics *s;
  int nbkts;

  aih->ai_accesses = 0;		/* clear */
  s = h_statistics(aih->ai_aarptab); /* get stats */
  if (s->hs_davg < AI_AARPTAB_REHASH_POINT)
    return;			/* nothing to do */
  log(LOG_BASE, "aarp: %d buckets, %d entries",
      s->hs_buckets, s->hs_used);
  log(LOG_BASE, "aarp: arp hash table average lookup distance %d.%02d",
      s->hs_davg / 100, s->hs_davg % 100);
  /* possibly delete old entries */
  if ((s->hs_buckets/2) > s->hs_used) {
    log(LOG_BASE, "aarp: buckets half filled, reconsider hash function");
    return;
  } else {
    nbkts = s->hs_buckets*2;
  }
  log(LOG_BASE, "aarp: rehashing table from %d buckets to %d buckets",
      s->hs_buckets, nbkts);
  h_rehash(aih->ai_aarptab, nbkts);
}

/* find an aarp table entry given an ethertalk node address */
private AARP_ENTRY *
aarptab_find(aih, pa)
AI_HANDLE *aih;
struct ethertalkaddr *pa;
{
  AARP_ENTRY *r;
  if ((r = (AARP_ENTRY *)h_member(aih->ai_aarptab, pa))) {
#ifdef AARP_DUMP_TABLE
    r->aae_used++;		/* used once more */
#endif
    if (aih->ai_accesses++ > AI_AARPTAB_EVAL_POINT)
      aarptab_speedup(aih);
  }
  return(r);
}

/* find an aarp table entry given an ethertalk node address */
/* if not found, return a new entry */
private AARP_ENTRY *
aarptab_get(aih, pa)
AI_HANDLE *aih;
struct ethertalkaddr *pa;
{
  AARP_ENTRY *r;
  int d, b;

  r = (AARP_ENTRY *)h_operation(HASH_OP_INSERT,aih->ai_aarptab,pa,-1,-1,&d,&b);
  if (r == NULL)
    return(NULL);
  if (r->aae_flags == 0) {
    /* EtherTalk II */
    log(LOG_LOTS, "New AARP mapping for node %d, bkt %d, dist %d\n",
	r->aae_pa.node);
    r->aae_aarptab = aih->ai_aarptab;	/* remember */
  } else {
    if (aih->ai_accesses++ > AI_AARPTAB_EVAL_POINT)
      aarptab_speedup(aih);
  }
#ifdef AARP_DUMP_TABLE
  r->aae_used++;		/* bump */
#endif
  return(r);
}

#ifdef notdef
/* delete an aarp table entry  */
private int
aarptab_delete(aih, aa)
AI_HANDLE *aih;
AARP_ENTRY *aa;
{

  aa->aae_flags = 0;		/* not okay */
  /* EtherTalk II */
  log(LOG_LOTS, "deleting arp entry: node %d", aa->aae_pa.node);
  en = (AARP_ENTRY *)h_delete(aih->ai_aarptab, aa->aae_pa.node);
  if (en != aa) {
    log(LOG_BASE, "when deleting arp entry, freed entry was %x, wanted %x",
	en, aa);
  }
  aa->aae_next = aarptab_free_list;
  aarptab_free_list = aa;	/* mark */
}
#endif

/* scan arp table and see if anything needs going */
aarptab_scan()
{
  struct timeval tv;
  AARP_ENTRY *n, *np, *en, *t;

  np = NULL, n = aarptab_node_list;
  while (n) {
    if (n->aae_ttl <= 0) {
      log(LOG_LOTS, "deleting arp entry: node %d", n->aae_pa.node);
      en = (AARP_ENTRY *)h_delete(n->aae_aarptab, &n->aae_pa);
      if (en != n) {
	log(LOG_BASE, "when deleting arp entry, freed entry was %x, wanted %x",
	    en, n);
      }
      if (np)
	np->aae_next = n->aae_next; /* unlink */
      else
	aarptab_node_list = n->aae_next; /* at head of list */
      /* link onto free list */
      t = n->aae_next;		/* remember next */
      n->aae_next = aarptab_free_list;
      aarptab_free_list = n;	/* mark */      /* delete it */
      n = t;			/* advance n */
      continue;			/* and skip regular advance */
    } else
      n->aae_ttl--;		/* dec. ttl */
    np = n;			/* remeber previous */
    n = n->aae_next;		/* advance n */
  }
  tv.tv_sec = AARP_SCAN_TIME;
  tv.tv_usec = 0;
  relTimeout(aarptab_scan, 0, &tv, TRUE);
}

/*
 * initialize AARP module
 *
 * arguments are: dev, devno of the interface
 *   maxnodes specfies the maximum number of nodes wanted on this
 *     interface (usually one)
 *
 * returns pointer to a AI_HANDLE - as caddr_t
 *
*/
caddr_t
aarp_init(dev, devno, maxnodes)
char *dev;
int devno;
int maxnodes;
{
  int aarp_listener();
  AI_HANDLE *aih = NULL;
  static int here_before = 0;
  int ph = -1;

  if (!here_before) {
    struct timeval tv;

    here_before = 1;
    aarptab_node_list = NULL;	/* clear */
    aarptab_free_list = NULL;
    tv.tv_sec = AARP_SCAN_TIME;
    tv.tv_usec = 0;
    relTimeout(aarptab_scan, 0, &tv, TRUE);
  }

  if ((ph = pi_open(ETHERTYPE_AARP, dev, devno)) < 0) {
    log(L_UERR|LOG_LOG,"pi_open: aarp_init");
    return(NULL);
  }
  if ((aih = (AI_HANDLE *)malloc(sizeof(AI_HANDLE))) == NULL)
    goto giveup;
  if ((aih->ai_nodes = (struct ai_host_node *)
       malloc(sizeof(struct ai_host_node)*maxnodes)) == NULL)
    goto giveup;

  /* clear host node table */
  bzero(aih->ai_nodes, sizeof(struct ai_host_node)*maxnodes);
  aih->ai_flags = 0;		/* clear flags for interface */
  aih->ai_numnode = 0;		/* no nodes yet */
  aih->ai_maxnode = maxnodes;	/* max # of nodes (usually 1) */
  aih->ai_ph = ph;
  /* get ethernet address */
  if ((pi_get_ethernet_address(ph, aih->ai_eaddr)) < 0)
    goto giveup;

  log(LOG_BASE, "Ethernet address for %s%d is %02x:%02x:%02x:%02x:%02x:%02x\n",
      dev, devno, 
      aih->ai_eaddr[0], aih->ai_eaddr[1], aih->ai_eaddr[2],
      aih->ai_eaddr[3], aih->ai_eaddr[4], aih->ai_eaddr[5]);

  /* setup arp table */
  if ((aih->ai_aarptab = aarptab_init()) == NULL)
    goto giveup;
  aih->ai_accesses = 0;		/* no access to table yet */

  /* establish listener now */
  pi_listener(aih->ai_ph, aarp_listener, (caddr_t)aih);
  return((caddr_t)aih);
 giveup:
  if (ph >= 0)
    pi_close(ph);
  if (aih) {
    if (aih->ai_nodes)
      free(aih->ai_nodes);
    free(aih);
  }
  return(NULL);
}

/*
 * get a host node address
 *
*/
export int
aarp_get_host_addr(aih, pa, which)
AI_HANDLE *aih;
struct ethertalkaddr *pa;
int which;
{
  if (aih->ai_numnode > which &&
      aih->ai_nodes[which].aihn_state == AI_NODE_OKAY) {
    *pa = aih->ai_nodes[which].aihn_pa; /* copy it */
    /* should we count pa node address? */
    return(1);			/* one byte handled for now */
  }
  return(-1);
}

/*
 * returns -1: can't be resolved
 *          0: resolving, come back later
 *          1: resolved
 *
 * Resolves the ethernet address of the specified destination.
 * Special cases are:
 *   dstnode == 0xff -> broadcast
 *   srcnode != ourselves - error
 *   dstnode == ourselves - our hw addr (maybe should be error if we
 *        can't send to ourselves)
 * In general, algorithm is:
 *  check internal table.  If entry is valid and if the ttl is valid
 *  return the address from the table
 * If not in the table or ttl is bad, then do a request -- however,
 *  the current packet WILL drop since the aarp request will not block
 *
*/
export int
aarp_resolve(aih, tpa, wantbr, eaddr)
AI_HANDLE *aih;
struct ethertalkaddr *tpa;
int wantbr;
u_char **eaddr;
{
  struct timeval *tv;
  AARP_ENTRY *aa;

  if (wantbr) {
    *eaddr = b_eaddr;	/* want to broadcast */
    return(1);
  }

  /* allow target to be ourselves */
  if (host_node(aih, tpa, AI_NODE_OKAY)) {
    *eaddr = aih->ai_eaddr;
    return(0);
  }

  /* find the node in the table */
  if ((aa = aarptab_find(aih, tpa)) != NULL && aa->aae_flags & AARP_OKAY) {
    if (aa->aae_flags & (AARP_PERM)) {
      *eaddr = aa->aae_eaddr;
      return(1);
    }
    if (aa->aae_ttl > 0) {
      *eaddr = aa->aae_eaddr;	/* yes, return valid */
      return(1);
    }
  }
  /* query on interface */
  aarp_request(aih, aa, tpa);
  return(0);
}

/*
 * call with hardware address, protocol address
 * flag is TRUE if insert is "hard" FALSE o.w.
 *  -- A HARD insert will smash what is there
 *  -- A SOFT insert will return FALSE if the ha's don't match and
 *       invalidate the cache entry
 * If the insert succeeds, then the ttl is bumped to the normal level
 *
 * Note: aarp_insert performs the gleaning functions of the aarp spec.
*/
export int
aarp_insert(aih, ha, pa, hard)
AI_HANDLE *aih;
u_char *ha;
struct ethertalkaddr *pa;
int hard;
{
  AARP_ENTRY *aa;
  struct timeval *tv;

  /* check to make sure ha isn't broadcast (or multicast)! */
  /* EtherTalk II fixup */
  log(LOG_LOTS, "Got mapping for %d", pa->node);
  dumpether(LOG_LOTS, "  Address", ha);

  aa = aarptab_get(aih, pa); /* find it or get a new one */
  if (aa->aae_flags & AARP_OKAY) {
    if (aa->aae_flags & (AARP_PERM)) /* don't reset these */
      return(FALSE);
    if (hard)
      /* EtherTalk II fixup */
      log(LOG_LOTS, "Resetting an old mapping for node %d", pa->node);
  }
  /* if arp entry is in cache and we aren't doing a hard update */
  /* and the hardware addresses don't match, don't do an update */
  if ((aa->aae_flags & AARP_OKAY) && !hard) {
    if (bcmp((caddr_t)aa->aae_eaddr, (caddr_t)ha, EHRD) != 0) {
      log(LOG_BASE, "AARP RESET - ethernet address mismatch");
      /* EtherTalk II fixup */
      log(LOG_BASE,"node number is %d", pa->node);
      dumpether(LOG_BASE, "incoming address", ha);
      dumpether(LOG_BASE, "cached address",aa->aae_eaddr);
      aa->aae_flags &= ~AARP_OKAY; /* there we go */
      return(FALSE);
    }
  } else {
    bcopy((caddr_t)pa, (caddr_t)&aa->aae_pa, sizeof(aa->aae_pa));
    bcopy((caddr_t)ha, (caddr_t)aa->aae_eaddr, EHRD); /* copy in mapping */
    aa->aae_flags = AARP_OKAY;	/* reset resolving flag if set too */
  }
  aa->aae_ttl = AARP_TTL;	/* reset */
  return(TRUE);
}

/*ARGSUSED*/
private
aarp_listener(fd, aih, proth)
int fd;				/* dummy */
AI_HANDLE *aih;
int proth;
{
  struct ethertalkaddr *dpa, *spa;
  byte *sha;
  struct ether_arp arp;

  struct ai_host_node *hn;

  /* note, we use read protocol because we cannot trust incoming etalk */
  /* addresses from DLI on Ultrix if sent to broadcast */
  if (pi_read(aih->ai_ph, &arp, sizeof(arp)) < 0)
    return;

  if (ntohs(arp.arp_hrd) != ARPHRD_ETHER || /* not ethernet? */
      ntohs(arp.arp_pro) != ETHERTYPE_APPLETALK || /* not appletalk? */
      arp.arp_hln != EHRD ||	/* wrong hrdwr length */
      arp.arp_pln != ETPL) {	/* wrong protocol length? */
    /* maybe log? */
    return;			/* yes, to one, kill packet */
  }

  dpa = (struct ethertalkaddr *)arp.arp_tpa; /* reformat */
  spa = (struct ethertalkaddr *)arp.arp_spa; /* reformat */
  /* copy these because sunos4.0 they are struct's rather than arrays */
  /* and we need to take the address and hate the way the warning */
  /* messages come up, besides saves us a bit on dereferncing */
  sha = (byte *)&arp.arp_sha;

  /* check dummy bytes? */
  /* this is the first one of these, so ...  the reason we have an & */
  /* in front of an array is that under sunos4.0, arp_sha, etc. are */
  /* now structs */
  if (bcmp((caddr_t)sha, (caddr_t)aih->ai_eaddr, EHRD) == 0) {
    log(LOG_LOTS, "Packet from self");
    return;
  }
  if (host_node(aih, spa, AI_NODE_OKAY)) {
    /* EtherTalk II fixup */
    log(LOG_BASE, "Address conflict %d\n", spa->node);
  }

  switch (ntohs(arp.arp_op)) {
  case ARPOP_PROBE:
    if ((hn = host_node(aih, spa, 0))) {
      switch (hn->aihn_state) {
      case AI_NODE_OKAY:
	aarp_reply(aih, &hn->aihn_pa, &arp); /* defend! */
	break;
      case AI_NODE_PROBE:
	hn->aihn_state = AI_NODE_UNUSED;
	break;
      default:
	break;
      }
      break;
    }
    aarp_probed(aih, sha, spa, arp); /* check over entry since we got probe */
    break;
  case ARPOP_REQUEST:
    if (host_node(aih, spa, 0))	/* drop if request from one of */
      break;			/* our address */
    /* probably should be more discriminating here */
    /* insert new entry */
    aarp_insert(aih, sha, (struct ethertalkaddr *)arp.arp_spa, TRUE); 
    if ((hn = host_node(aih, dpa, AI_NODE_OKAY)))
      aarp_reply(aih, &hn->aihn_pa, &arp);
    break;
  case ARPOP_REPLY:
    /* check to see that dpa isn't one we are probing */
    /* if it is, then things are bad for that potential addr */

    if ((hn = host_node(aih, dpa, AI_NODE_PROBE))) {
      hn->aihn_state = AI_NODE_UNUSED;
      break;
    }
    /* drop if source was self (whether ready or not) */
    /* don't need check before here because replies are not broadcast, */
    /* so why would we get it :-) */
    if (host_node(aih, spa, 0))
      break;
    /* mark in our tables */
    aarp_insert(aih, sha, spa, TRUE);
    break;
  }
}

/* AARP PROBE: some hooks in the listener too */
/*
 * initialize and start AARP probe process
 *
*/
struct aarp_aphandle {
  AI_HANDLE *aaph_aih;		/* protocol handle */
  AARP_ENTRY *aaph_ae;		/* request: point to node handle */
  struct ai_host_node *aaph_node; /* probe: which node # probing */
  struct ether_arp aaph_arp;	/* arp */
  int (*aaph_callback)();	/* callback on success on request/probe */
  caddr_t aaph_callback_arg;	/* and initial argument */
  int aaph_callback_result;	/* this is the result */
  int aaph_tries;		/* tries to do */
};

/*
 * acquire an ethertalk node 
 * doesn't try new node address 
 * callback is (*callback)(callback_arg, result)
 *  where result < 0 if couldn't
 *        result >= 0 ==> index of host node address for get host addr
 *
 * return: > 0 - bad node
 *         = 0 - okay
 *         < 0 - internal error
 *
*/

/* probe state */
export int
aarp_acquire_etalk_node(aih, initial_node, callback, callback_arg)
AI_HANDLE *aih;
struct ethertalkaddr *initial_node;
int (*callback)();		/* callback routine */
caddr_t callback_arg;		/* arg for callback */
{
  struct aarp_aphandle *probe;
  struct ai_host_node *ahn;
  int whichnode = -1;
  int i;
    
  /* bad node */
  /* EtherTalk II fixup */
  if (initial_node->node == 0 || initial_node->node == 255)
    return(1);
  if (aarptab_find(aih, initial_node) || host_node(aih, initial_node, 0)) {
    /* callback ? */
    return(1);
  }

  /* get probe structure */
  probe = (struct aarp_aphandle *)malloc(sizeof(struct aarp_aphandle));
  if (probe == NULL)
    return(-1);
  m_probe++;
  for (ahn = aih->ai_nodes, i = 0; i < aih->ai_numnode; i++, ahn++) {
    if (ahn->aihn_state)	/* node in use or in probe */
      continue;
    whichnode = i;		/* got a good one, ahn set */
    break;
  }
  if (whichnode < 0) {		/* no node allocate? */
    if (aih->ai_numnode >= aih->ai_maxnode) /* can't */
      return(-1);
    whichnode = aih->ai_numnode++; /* next node */
    ahn = &aih->ai_nodes[whichnode]; /* get node */
  }

  /* initialize probe arp packet */
  bzero(&probe->aaph_arp, sizeof(probe->aaph_arp));
  probe->aaph_arp.arp_hrd = htons(ARPHRD_ETHER);
  probe->aaph_arp.arp_pro = htons(ETHERTYPE_APPLETALK);
  probe->aaph_arp.arp_op = htons(ARPOP_PROBE);
  probe->aaph_arp.arp_hln = EHRD;
  probe->aaph_arp.arp_pln = ETPL;
  bcopy((caddr_t)aih->ai_eaddr, (caddr_t)&probe->aaph_arp.arp_sha, EHRD);

  bcopy((caddr_t)initial_node, (caddr_t)probe->aaph_arp.arp_spa, ETPL);
  bcopy((caddr_t)initial_node, (caddr_t)probe->aaph_arp.arp_tpa, ETPL);

  probe->aaph_tries = AARP_PROBE_TRY;

  probe->aaph_callback = callback;
  probe->aaph_callback_arg = callback_arg;

  probe->aaph_aih = aih;	/* handle for interface */
  probe->aaph_ae = NULL;	/* mark as none */

  probe->aaph_callback_result = whichnode; /* mark */
  probe->aaph_node = ahn;	/* remember it */
  ahn->aihn_state = AI_NODE_PROBE; /* mark in probe state */
  ahn->aihn_pa = *initial_node;	/* copy pa */

  /* do first probe */
  probe_and_request_driver(probe);
  return(0);			/* okay, we are trying! */
}

/* main probe and request driver */
private
probe_and_request_driver(aphandle)
struct aarp_aphandle *aphandle;
{
  register AARP_ENTRY *enp;

  struct timeval tv;
  struct ai_host_node *ahn;
  AI_HANDLE *aih = aphandle->aaph_aih;

  /* either of these can be running or both - both really doesn't */
  /* make much sense though */
  enp = aphandle->aaph_ae;		/* get pointer to node handle */
  ahn = aphandle->aaph_node;

  if (enp && ahn) {
    log(LOG_LOG, "internal error: probe and request set in aarp driver\n");
    log(LOG_LOG|L_EXIT, "fatal: please report\n");
  }

  if (enp && (enp->aae_flags & AARP_RESOLVING) == 0)
    goto dofree;
  if (ahn && ahn->aihn_state == AI_NODE_UNUSED) {
    /* tell caller that probe for the node address failed */
    if (aphandle->aaph_callback)
      (*aphandle->aaph_callback)(aphandle->aaph_callback_arg, -1);
    goto dofree;
  }
  if (aphandle->aaph_tries <= 0) { /* nothing left to do? */
    /* if we are out of tries, then node has been acquired, hurrah! */
    if (ahn) {
      ahn->aihn_state = AI_NODE_OKAY;
    }
    if (enp) {
      enp->aae_flags &= ~AARP_RESOLVING; 
    }
    if (aphandle->aaph_callback)
      (*aphandle->aaph_callback)(aphandle->aaph_callback_arg,
				 aphandle->aaph_callback_result);
    goto dofree;
  }
  /* set timeout to next try */
  aphandle->aaph_tries--;
  if (enp) {
    /* should decay based on number of tries */
    tv.tv_sec = AARP_REQUEST_TIMEOUT_SEC;
    tv.tv_usec = AARP_REQUEST_TIMEOUT_USEC;
  }
  if (ahn) {
    /* set timeout to next try */
    tv.tv_sec = AARP_PROBE_TIMEOUT_SEC;
    tv.tv_usec = AARP_PROBE_TIMEOUT_USEC;
  }
  if (pi_write(aih->ai_ph,&aphandle->aaph_arp, sizeof(struct ether_arp),
		    b_eaddr) < 0) 
    log(LOG_BASE|L_UERR, "pi_write failed: aarp driver");
  /* setup timeout to next (relative timeout) */
  relTimeout(probe_and_request_driver, aphandle, &tv, TRUE);
  return;
dofree:
  while (remTimeout(probe_and_request_driver, aphandle))
    /* remove while some on queue: NULL STATEMENT */;
  /* address was resolving, nothing left */
  free((caddr_t)aphandle);
  f_probe++;
}


/* 
 * got a probe on the specified ha, pa pair and pa is not one of ours
 * 
 * mark the mapping pair as suspect.  If the specified ha is
 * different, then mark it as very suspect.  In both cases, the
 * "suspect" flag is moving down the ttl of the mapping.
 *
*/
private void
aarp_probed(aih, ha, pa, arp)
AI_HANDLE *aih;
u_char *ha;
struct ethertalkaddr *pa;
struct ether_arp *arp;
{
  AARP_ENTRY *aa;
  struct timeval *tv;
  int nttl;

  if ((aa = aarptab_find(aih, pa)) == NULL) /* find entry */
    return;			/* nothing */
  if ((aa->aae_flags & AARP_OKAY) == 0) /* nothing to do? */
    return;			/* ;right */
  /* get reduction factor on ttl */
  nttl = (bcmp((caddr_t)ha, (caddr_t)aa->aae_eaddr, EHRD) == 0) ?
    AARP_PROBED_SAME : AARP_PROBED_DIFF;
  /* update ttl to lessor of new or old */
  if (nttl < aa->aae_ttl)
    aa->aae_ttl = nttl;
}

/*
 * reply to an arp request packet
 *
 * note, smashes the arp packet
 *
*/
private void
aarp_reply(aih, pa, arp)
AI_HANDLE *aih;
struct ethertalkaddr *pa;
struct ether_arp *arp;
{
  caddr_t sha, tha;

  sha = (caddr_t)&arp->arp_sha;	/* need & because can be struct */
  tha = (caddr_t)&arp->arp_tha;

  bcopy(sha, tha, EHRD);
  bcopy((caddr_t)arp->arp_spa, (caddr_t)arp->arp_tpa, ETPL);
  arp->arp_op = htons(ARPOP_REPLY);
  bcopy((caddr_t)aih->ai_eaddr, sha, EHRD);
  /* copy in our node */
  bcopy((caddr_t)pa, (caddr_t)arp->arp_spa, ETPL);
  if (pi_write(aih->ai_ph, arp, sizeof(*arp), tha) < 0) {
    log(LOG_LOG|L_UERR, "etsend");
  }
}

/*
 * do an aarp request
 * - doesn't check that target is ourselves
*/
private void
aarp_request(aih, snode, tpa)
AI_HANDLE *aih;
AARP_ENTRY *snode;
struct ethertalkaddr *tpa;
{
  AARP_ENTRY *aa;
  struct aarp_aphandle *ap;

  if ((aa = aarptab_get(aih, tpa)) == NULL) /* get new */
    return;
  if (aa->aae_flags & (AARP_RESOLVING))
    return;
  /* bad condition */
  if (aa->aae_flags & (AARP_PERM))
    return;
  ap = (struct aarp_aphandle *)malloc(sizeof(struct aarp_aphandle));
  m_probe++;
  aa->aae_pa = *tpa;
  aa->aae_flags |= AARP_RESOLVING;
  aa->aae_flags &= ~AARP_OKAY;	/* make sure! (can be) */

  /* establish arp packet */
  bzero(&ap->aaph_arp, sizeof(ap->aaph_arp));
  ap->aaph_arp.arp_op = htons(ARPOP_REQUEST);
  ap->aaph_arp.arp_hrd = htons(ARPHRD_ETHER);
  ap->aaph_arp.arp_pro = htons(ETHERTYPE_APPLETALK);
  ap->aaph_arp.arp_hln = EHRD;
  ap->aaph_arp.arp_pln = ETPL;
  bcopy((caddr_t)snode->aae_eaddr, (caddr_t)&ap->aaph_arp.arp_sha, EHRD);
  bcopy((caddr_t)&snode->aae_pa, (caddr_t)ap->aaph_arp.arp_spa,
	sizeof(struct ethertalkaddr));
  bcopy((caddr_t)tpa, (caddr_t)ap->aaph_arp.arp_tpa, sizeof(*tpa));

  ap->aaph_ae = aa;		/* remember this for later */
  ap->aaph_tries = AARP_REQUEST_TRY;
  ap->aaph_node = NULL;		/* clear this */
  ap->aaph_aih = aih;
  ap->aaph_callback = NULL;

  probe_and_request_driver(ap);
}


/*
 * See if the specified ethertalk address "n" is a host address
 * return true if it is, false o.w.
 *
 * if flag is set, then it indicates that we only want to check against
 *  host nodes in the state flag is set to.
 *
*/
private struct ai_host_node *
host_node(aih, n, flag)
AI_HANDLE *aih;
struct ethertalkaddr *n;
{
  register int i = aih->ai_numnode;
  register struct ai_host_node *ai_nodes = aih->ai_nodes;

  for ( ; i  ; ai_nodes++, i--)
    if (ai_nodes->aihn_state) {
      if (flag && ai_nodes->aihn_state != flag)
	continue;
      /* EtherTalk II fixup */
      if (ai_nodes->aihn_pa.node && ai_nodes->aihn_pa.node == n->node)
	return(ai_nodes);
    }
  return(NULL);
}


export
aarp_dump_stats(fd, ah)
FILE *fd;
AI_HANDLE *ah;
{
  struct hash_statistics *s = h_statistics(ah->ai_aarptab);

  fprintf(fd, " aarp hash table statistics, %d nodes in use\n",
	  s->hs_used);
  fprintf(fd, "\t%d lookups since last rehash, average distance %.02f\n",
	  s->hs_lnum, s->hs_lnum ? ((float)s->hs_lsum / s->hs_lnum) : 0.0);
  fprintf(fd, "\t%d lookups total, average distance %.02f\n",
	  s->hs_clnum, s->hs_clnum ? ((float)s->hs_clsum / s->hs_clnum) : 0.0);
  putc('\n', fd);
  fprintf(fd, " %d aarptab entries allocated\n", m_aarptab);
  fprintf(fd, " %d probe/request handles, %d freed\n", m_probe, f_probe);
  putc('\n', fd);
}

export
aarp_dump_tables(fd, aih)
FILE *fd;
AI_HANDLE *aih;
{
  AARP_ENTRY *n;

#ifdef AARP_DUMP_TABLE
  fprintf(fd," ARP table dump for interface\n");
  for (n = aarptab_node_list; n ; n = n->aae_next) {
    if (aih->ai_aarptab != n->aae_aarptab || n->aae_flags == 0)
      continue;
    fprintf(fd,
       "  node %d, %02x:%02x:%02x:%02x:%02x:%02x, used %d flags %s%s%sttl %d\n",
	    n->aae_pa.node, 
	    n->aae_eaddr[0], n->aae_eaddr[1], n->aae_eaddr[2], 
	    n->aae_eaddr[3], n->aae_eaddr[4], n->aae_eaddr[5],
	    n->aae_used,
	    AARP_FNAME_OKAY(n->aae_flags),
	    AARP_FNAME_RESOLVING(n->aae_flags),
	    AARP_FNAME_PERM(n->aae_flags), n->aae_ttl);
  }
  putc('\n', fd);
#endif
}
SHAR_EOF
if test 25817 -ne "`wc -c < 'aarp.c'`"
then
	echo shar: "error transmitting 'aarp.c'" '(should have been 25817 characters)'
fi
chmod 440 'aarp.c'
fi
if test -f 'aarp.h'
then
	echo shar: "will not over-write existing file 'aarp.h'"
else
cat << \SHAR_EOF > 'aarp.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:00 $
 * $Header: /src/local/mac/cap/etalk/RCS/aarp.h,v 1.2 88/09/14 10:19:00 cck Rel $
 * $Revision: 1.2 $
*/

/*
 * aarp.h Apple Address Resolution Protocol interface
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  September 1988  CCKim Created
 *
*/

/*
 * intializes aarp for this interface: upto maxnode nodes for this interface 
*/
caddr_t aarp_init( /* char *dev, int devno, int maxnode */);

/*
 * returns a node address for this interface.  index specifies which
 *  one (can have multiple)
*/
int aarp_get_host_addr(/*struct ethertalkaddr *pa, int index*/);


/*
 * resolves a ethertalk node address into an ethertalk address 
 * returns pointer to address in eaddr
 * args: caddr_t ah, struct ethertalkaddr *pa,
 *       boolean wantbr, u_char **eaddr
 *
*/ 
int aarp_resolve();

/*
 * inserts the a mapping into the aarp table
 *
 * if flag is "true" then will override any mappings already there
 *  [used by ethertalk for gleaning]
 * 
 * args: caddr_t ah, u_char ha[6], struct ethertalkaddr *pa, int flag
 *
*/
int aarp_insert();

/*
 * acquire an ethertalk node address 
 * tries to acquire the ethertalk node address specfied in node.
 * calls back with the host address index: 
 *     (*callback)(callback_arg, index)
 *
 * args: caddr_t ah, struct ethertalkaddr *node, int (*callback)(),
 *       caddr_t callback_arg
 *
*/
int aarp_acquire_etalk_node();

SHAR_EOF
if test 1909 -ne "`wc -c < 'aarp.h'`"
then
	echo shar: "error transmitting 'aarp.h'" '(should have been 1909 characters)'
fi
chmod 440 'aarp.h'
fi
if test -f 'aarp_defs.h'
then
	echo shar: "will not over-write existing file 'aarp_defs.h'"
else
cat << \SHAR_EOF > 'aarp_defs.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:02 $
 * $Header: /src/local/mac/cap/etalk/RCS/aarp_defs.h,v 1.5 88/09/14 10:19:02 cck Rel $
 * $Revision: 1.5 $
*/

/*
 * aarp_defs.h Apple Address Resolution Protocol definitions
 *
 * Meant only for the aarp module!
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August 1988  CCKim Created
 *
*/

#define AARP_DUMP_TABLE "yes" /* turn this on */

/*
 * AARP Ethernet type
 *
*/
#ifndef ETHERTYPE_AARP 0x80f3
# define ETHERTYPE_AARP 0x80f3
#endif

/* probe op, define if not defined */
#ifndef ARPOP_PROBE
# define ARPOP_PROBE 3
#endif

/*
 * AARP table management
 *
*/

/* An ethertalk node address (should probably merge with aarptab) */
typedef struct aarp_entry {
  struct ethertalkaddr aae_pa;	/* protocol address */
  int aae_flags;		/* flags */
#define AARP_OKAY 0x1		/* resolved */
#define AARP_RESOLVING 0x2	/* trying to resolve this */
#define AARP_PERM 0x4		/* permanent (not used) */
#define AARP_FNAME_OKAY(f) ((f&AARP_OKAY) ? "okay " : "")
#define AARP_FNAME_RESOLVING(f) ((f&AARP_RESOLVING) ? "resolving " : "")
#define AARP_FNAME_PERM(f) ((f&AARP_PERM) ? "perm " : "")
  u_char aae_eaddr[EHRD];	/* hardware address */
  int aae_ttl;			/* time to live */
  int aae_used;			/* # of times used */
  struct aarp_entry *aae_next;	/* next in list of all */
  caddr_t aae_aarptab;		/* back pointer */
} AARP_ENTRY;


/* time to live */
#define AARP_TTL 5		/* 4*AARP_SCAN_TIME */
#define AARP_SCAN_TIME (60)	/* scan every minute  */
/* we adjust ttls if an incoming probe comes in */
/* if the ha is the same, ttl gets dropped.  if the ha */
/* is different, the ttl is smacked dead */ 
#define AARP_PROBED_SAME 1	/* set time to live to AARP_SCAN_TIME */
#define AARP_PROBED_DIFF 0	/* set time to live to zero */

/*
 * AARP probe/request managment
 *
*/
/* number of times to probe an address */
#define AARP_PROBE_TRY 15
/* timeout between probes */
#define AARP_PROBE_TIMEOUT_SEC 0
#define AARP_PROBE_TIMEOUT_USEC 100000 /* 1/10 second */
/* same for requests */
#define AARP_REQUEST_TRY 10
#define AARP_REQUEST_TIMEOUT_SEC 0
#define AARP_REQUEST_TIMEOUT_USEC 250000 /* 1/4th second */

/*
 * AARP table/interface management
 *
*/
/* number of buckets in the aarp hash table */
#define AARP_TAB_SIZE 15

/*
 * one of these are created for every "interface" open
 *
*/
typedef struct aarp_interface_handle {
  int ai_ph;			/* protocol handle */
  caddr_t ai_aarptab;		/* aarp table */
  int ai_accesses;		/* count of access to arp tabl */
#define AI_AARPTAB_EVAL_POINT 2000
#define AI_AARPTAB_REHASH_POINT 200 /* more than avg. of 2 access */
  struct ai_host_node {		/* host's node table */
    struct ethertalkaddr aihn_pa; /* our protocol address */
    int aihn_state;
#define AI_NODE_UNUSED 0	/* node not in use */
#define AI_NODE_PROBE -1	/* node in probe state */
#define AI_NODE_OKAY 1		/* node okay */
  } *ai_nodes;
  int ai_maxnode;
  int ai_numnode;		/* number of nodes in use */
  int ai_flags;			/* interface flags */
  u_char ai_eaddr[EHRD];	/* enet addr */
  /* probably should return this integer instead of the handle */
} AI_HANDLE;


/* exported procedure definitions */
export caddr_t aarp_init();
export int aarp_resolve();
export int aarp_insert();
export int aarp_acquire_etalk_node();

SHAR_EOF
if test 3756 -ne "`wc -c < 'aarp_defs.h'`"
then
	echo shar: "error transmitting 'aarp_defs.h'" '(should have been 3756 characters)'
fi
chmod 440 'aarp_defs.h'
fi
if test -f 'bridge_desc'
then
	echo shar: "will not over-write existing file 'bridge_desc'"
else
cat << \SHAR_EOF > 'bridge_desc'
#
# The bridge description file is a list of the valid ports for a
# particular Unix AppleTalk Bridge (UAB).
# 
# In order to minimize the maintaince headache, one may use the host
# name as a selector.
#
# Each port description is entered on a single line.
#
# The first item in a line is the host selector field.  It can be the
# host name (as returned by gethostname).  In addition, you can use % to
# match any character.  "*" can be used to match any host.  Finally, you
# can use "*" at the end of a string to complete a match.  (Allowing "*"
# at both the beginning and end or at an arbritrary location is a pain
# because it is an unanchored search -- would have to use a regular
# expression matcher to do this -- ugh).  [MUST BE SPECIFIED]
#
# The second field contains a tuple that specifies the interface's
# link access protocol (LAP) and any device specific data required.
#
# Valid LAP method specifications:
#    ELAP - EtherTalk Link Access Protocol 
#    EtherTalk - same as above
# [MUST BE SPECIFIED]
#
# The device specific data consists of a "device name" followed by an
# colon and a "device number".  If the colon is omitted, the device
# number is assumed to be zero.
#
# For Ethertalk, this should be interpreted as a ethernet "tap" device
# (SunOS, Ultrix).  For example, "ie:1" for ethertalk on interface ie1.
#
# (Note, this is subject to change)
# 
# The third field specifies the local demultiplexing delivery
# mechanism for delivery of DDP packets not destined for the bridge
# process.  Currently defined mechanisms are: "none" which says there
# will be other client processes; "mkip" - modified version of kip
# style udp encapsulation using a different udp port range.
# (Hopefully, there will be a way to do direct kip, asynch appletalk,
# etc. in the future)
# [MUST BE SPECIFIED]
#
# The fourth and last field specifies two items paired in a
# [<item1>,<item2>] format.  The first is the DDP network that should
# be associated with this port.  If you specify zero, the ddp network
# number will be acquired via RTMP if there are other bridges that
# know the network number.  Note that only a single network is allowed
# at this point.  The network number may be specified as <number> or
# <high byte>.<low byte>.  In both cases, the number may be decimal
# (no leading zero), octal (leading zero), or hexadecimal (leading 0x
# or 0X).
#
# The second item specifies the zone name associated with
# the ddp network associated with this port.  If it is not specified,
# it will be acquired via ZIP if there are other bridges on the
# network that know the zone name.  Note: you may omit the comma if
# you do not wish to specify a zone.
#
# note, \ can be used to quote a character (like a space in the zone
# name :-)  A "\" at the end of a line continues the line.  Carriage
# return and line feed both terminate a line.
#
#  You should order the file from more specific to less specific (in
#  terms of host name matches.  Once a match has been found, then only
#  matches with the exactly same pattern will succeed!

#hostname	[type,data]	local	[network,zone]
# cunixc is connected to the machine room cable
 cunixc		[elap,ni]	mkip	[0]

# jolt is connected to backbone on qe0
# jolt is sometimes connected on qe1 to a private network
 jolt 		[ethertalk,qe]	mkip	[0]
 jolt 		[ethertalk,qe:1] none	[1.0,ETHERZONE]

# cunixa,c,d (only left) are on the machine room cable
 cunix%		[ethertalk,ie]	mkip	[0]

# cuccb qe0 is on the backbone
# cuccb qe1 is the mudd microlab private network
 cuccb		[ethertalk,qe]	mkip	[0]
 cuccb		[ethertalk,qe]	none	[0]


# this is dummy entry
 somewhere	[ethertalk,se]	none
SHAR_EOF
if test 3644 -ne "`wc -c < 'bridge_desc'`"
then
	echo shar: "error transmitting 'bridge_desc'" '(should have been 3644 characters)'
fi
chmod 440 'bridge_desc'
fi
if test -f 'ddpport.c'
then
	echo shar: "will not over-write existing file 'ddpport.c'"
else
cat << \SHAR_EOF > 'ddpport.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:03 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/ddpport.c,v 1.8 88/09/14 10:19:03 cck Rel $";
static char revision[] = "$Revision: 1.8 $";

/*
 * ddpport.c - ddp port manager
 *
 * Handles the ddp port which sits on the interface boundary between
 * DDP and LAP.  In addition, it carries the "local delivery" or
 * demuxing information necessary to pass data to individual modules
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 4, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/param.h>
#ifndef _TYPES
# include <sys/types.h>
#endif
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>
#include <netat/compat.h>
#include "ddpport.h"
#include "log.h"

private PORT *port_list = NULL;

#define LOG_HIGH 1
#define LOG_STD 2

/*
 * Establish an open port.
 *  ddp_net, ddp_node - ddp network/node to associate with this port
 *   (0 if not seeded) 
 *  zone - zone if any (null if not)
 *  node - node id of this port (required)
 *  local_handle - local data handle for caller
 *  sendrtn - send data routine
 *  map_fromddp - map ddp net, node to lap node
 *  map_laptoddp - map inverse (not used)
 *  demuxer - demux structure that describes the local delivery mechanism
 *
*/
export PORT_T
port_create(ddp_net, ddp_node, zone, node, flags, local_handle,
	    sendrtn, map_fromddp, map_laptoddp, demuxer)
word ddp_net;
word ddp_node;
byte *zone;
NODE *node;
int flags;
caddr_t local_handle;
int (*sendrtn)();
NODE *(*map_fromddp)();
int (*map_laptoddp)();
struct mpxddp_module *demuxer;
{
  struct route_entry *re;
  PORT *port;

  if ((port = (PORT *)malloc(sizeof(PORT))) == NULL) {
    log(LOG_HIGH|L_UERR, "malloc for port failed");
    return(NULL);
  }
  port->p_next = NULL;
  port->p_flags = flags|PORT_ISCONNECTED; /* mark here */
  port->p_ddp_node = ddp_node;	/* mark ddp node */
  port->p_ddp_net = 0;		/* mark as unknown for now */
  port->p_zonep = NULL;		/* mark as unknown for now */
  port->p_id = node;
  port->p_local_data = local_handle;
  port->p_send_if = sendrtn;
  port->p_map_lap_if = map_fromddp;
  port->p_map_ddp_if = map_laptoddp;
  port->p_mpx = NULL;
  if (ddp_net) {
    port_net_ready(port, ddp_net, NULL); /* this will set ddp_net properly */
    if (route_add_host_entry(port, ddp_net, zone) < 0) {
      log(LOG_HIGH,"***couldn't add host route for port %d, net %d.%d",
	  port, nkipnetnumber(ddp_net), nkipnetnumber(ddp_net));
    }
    if (zone)			/* if zone, mark that too */
      port_zone_known(port, zone);
  }
  /* wait until after we set zone, etc if we did */
  if (demuxer) {
    if (!port_setdemuxer(port, demuxer))
      log(LOG_HIGH, "couldn't initialize local divery via %s on port %d\n",
	  demuxer->mpx_name,port);
  }
  /* may need to call a lower level grab routine (e.g. if working with */
  /* kernel ddp) */
  return(port);
}

/*
 * set and initialize local delivery: can be called later if not set
 * in port create.
 * returns true,false
 *
*/
export
port_setdemuxer(port, mpx)
PORT_T port;
struct mpxddp_module *mpx;
{
  int i;

  if (mpx == NULL)
    return(TRUE);
  
  if (!mpx->mpx_init || (i = mpx->mpx_init()) < 0) /* init multiplexor */
    return(FALSE);
  if (mpx->mpx_grab)		/* if socket grabber */
    ddp_reopen(mpx->mpx_grab, i, NULL);	/* then call it */
  port->p_mpx_hdl = i;		/* remember handle */
  /* port is ready already! */
  if (mpx->mpx_havenode)	/* always mark node (must be known) */
    (*mpx->mpx_havenode)(i, port->p_ddp_node);
  /* if netready, send down net */
  if ((port->p_flags & PORT_NETREADY) && mpx->mpx_havenet)
      (*mpx->mpx_havenet)(i, port->p_ddp_net, port->p_ddp_node);
  /* if portready then send down zone (==> netready) */
  if ((port->p_flags & PORT_ISREADY) && mpx->mpx_havezone)    
    (*mpx->mpx_havezone)(i, port->p_zonep);
  port->p_mpx = mpx;
  return(TRUE);
}

/*
 * get the head of the "lap" interface port list
 *
*/
export PORT_T
port_list_start()
{
  return(port_list);
}

/*
 * port's net is ready, set vars: return TRUE if was ready
 *
 * (Is this really sufficient?)
 *
*/
export boolean
port_net_ready(port, net, sid, zone)
PORT_T port;			/* port */
word net;			/* ddp network for port */
NODE *sid;			/* node the information came from */
byte *zone;			/* zone */
{
  if (port == NULL) {		/* how did this happen? */
    log(LOG_HIGH, "port null in port_net_ready");
    return(FALSE);
  }
  if (port->p_ddp_net == 0) {
    port->p_ddp_net = net;
    port->p_next = port_list;	/* link into list */
    port_list = port;
    port->p_flags |= PORT_NETREADY; /* mark sure marked ready */
    /* set net & bridge info (bridge is ourselves) */
    if (port->p_mpx && port->p_mpx->mpx_havenet)
      (*port->p_mpx->mpx_havenet)(port->p_mpx_hdl, net, port->p_ddp_node);
    return(FALSE);
  }
  if (port->p_ddp_net != net) {
    log(LOG_STD, "***net mismatch: port %d's net is %d.%d, received %d.%d",
	port, nkipnetnumber(port->p_ddp_net),
	nkipsubnetnumber(port->p_ddp_net),
	nkipnetnumber(net), nkipsubnetnumber(net));
    if (sid)
      log(LOG_STD, "offending node: %s",
	  node_format(sid));
  } 
  return(TRUE);
}

/*
 * zone acquired, returns TRUE, FALSE
 *
*/
export boolean
port_zone_known(port,zone)
PORT_T port;
byte *zone;
{
  if (port == NULL) {		/* how did this happen? */
    log(LOG_HIGH, "port null in port_zone_ready");
    return(FALSE);
  }
  if ((port->p_flags & PORT_NETREADY) == 0)
    return(FALSE);
  port->p_flags |= PORT_ISREADY; /* mark sure marked ready */
  /* zone storage is kept */
  port->p_zonep = zone;		/* remember it */
  if (port->p_mpx && port->p_mpx->mpx_havezone)
    (*port->p_mpx->mpx_havezone)(port->p_mpx_hdl, zone);
  return(TRUE);
}
SHAR_EOF
if test 6463 -ne "`wc -c < 'ddpport.c'`"
then
	echo shar: "error transmitting 'ddpport.c'" '(should have been 6463 characters)'
fi
chmod 440 'ddpport.c'
fi
if test -f 'ddpport.h'
then
	echo shar: "will not over-write existing file 'ddpport.h'"
else
cat << \SHAR_EOF > 'ddpport.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:06 $
 * $Header: /src/local/mac/cap/etalk/RCS/ddpport.h,v 1.6 88/09/14 10:19:06 cck Rel $
 * $Revision: 1.6 $
*/

/*
 * ddpport.c - ddp port manager
 *
 * Handles the ddp port which sits on the interface boundary between
 * DDP and LAP.  In addition, it carries the "local delivery" or
 * demuxing information necessary to pass data to individual modules
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 4, 1988  CCKim Created
 *
*/

#ifndef _DDP_PORT_INCLUDED
#define _DDP_PORT_INCLUDED "yes"

#include "mpxddp.h"
#include "node.h"

/* this struct should really be hidden totally */
/*
 * PORT, struct port
 *
 * First, it describes the "lap" level parameters for a particular
 * interface.  Second, it describes the ddp network attached to that
 * interface.  ("lap" could be alap, elap, etc).
 *
 * Note: no routine should access port directly
 *
*/
typedef struct port {
  struct port *p_next;		/* pointer to next struct */
  int p_flags;			/* if any */
#define PORT_ISCONNECTED 0x1
/* could do at lower level, more efficient here? */
#define PORT_WANTSLONGDDP 0x2	/* wants to output long ddp */
#define PORT_NEEDSBROADCAST 0x4	/* needs broadcasts sent back */
#define PORT_FULLRTMP 0x8	/* full rtmp (advertise bridge) */
#define PORT_NETREADY 0x10	/* we know the net of the port */
#define PORT_ISREADY 0x20	/* port info complete (net,node,zone) */
  word p_ddp_net;		/* primary ddp network of port */
  byte p_ddp_node;		/* primary ddp node of port */
  byte *p_zonep;		/* remember our zone */
  NODE *p_id;			/* lap level id */
  caddr_t p_local_data;		/* local data for port manager */
  int (*p_send_if)();		/* send routine for that port */
  NODE *(*p_map_lap_if)();	/* ddp node to "lap" node mapper*/
  int (*p_map_ddp_if)();	/* map "lap" node to ddp node */
  /* other */
  struct mpxddp_module *p_mpx;	/* remember demuxer */
  int p_mpx_hdl;		/* mpx handle, in case for each port */
} PORT;

/* PORT_T may change to an int */
typedef struct port *PORT_T;

#define PORT_BAD(p) ((p) == NULL)
#define PORT_GOOD(p) ((p) != NULL)
#define PORT_NEXT(p) ((p)->p_next)
#define PORT_LIST_START() ((PORT_T)port_list_start())
#define PORT_FLAGS(p) ((p)->p_flags)
/* port is open */
# define PORT_CONNECTED(p) ((p)->p_flags & PORT_ISCONNECTED)
/* network defined */
# define PORT_READY(p) ((p)->p_flags & PORT_ISREADY)
# define PORT_WANTS_LONG_DDP(p) ((p)->p_flags & PORT_WANTSLONGDDP)
# define PORT_NEEDS_BROADCAST(p) ((p)->p_flags & PORT_NEEDSBROADCAST)
# define PORT_ISBRIDGING(p) ((p)->p_flags & PORT_FULLRTMP)
#define PORT_DDPNET(p) ((p)->p_ddp_net)
#define PORT_DDPNODE(p) ((p)->p_ddp_node)
#define PORT_GETLOCAL(p, type) ((type)(p)->p_local_data)
#define PORT_NODEID(p) ((p)->p_id)
/* send if possible */
#define PORT_SEND(p,dst,laptype,hdr,hdrlen,data,datalen) \
  (((p)->p_send_if) ? \
  ((*(p)->p_send_if)((p),(dst),(laptype),(hdr),(hdrlen),(data),(datalen))) : \
  -1)
#define PORT_MAKENODE(p,net,node) \
  (((p)->p_map_lap_if) ? \
  ((*(p)->p_map_lap_if)((p),(net),(node))) : \
  NULL)
#define PORT_DEMUX(p, ddp, data, datalen) \
  (((p)->p_mpx && (p)->p_mpx->mpx_send_ddp) ? \
  ((*(p)->p_mpx->mpx_send_ddp)((p)->p_mpx_hdl, (ddp), (data), (datalen))) : \
  -1)
/* set the port demuxer */
#define PORT_SETDEMUXER(p, desc) (port_setdemuxer((p), (desc)))
#define PORT_NET_READY(p, net, sid) (port_net_ready((p),(net),(sid)))
#define PORT_ZONE_KNOWN(p, zone) (port_zone_known((p),(zone)))
#define PORT_GRAB(p, skt) (((p)->p_mpx && (p)->p_mpx->mpx_grab) ? \
			   ((*(p)->p_mpx->mpx_grab)(skt, NULL)) : -1)
export PORT_T port_create();
export boolean port_set_demuxer();
export PORT_T port_list_start();
export boolean port_net_ready();
export boolean port_zone_known();

#endif /* INCLUDE THIS FILE */


SHAR_EOF
if test 4252 -ne "`wc -c < 'ddpport.h'`"
then
	echo shar: "error transmitting 'ddpport.h'" '(should have been 4252 characters)'
fi
chmod 440 'ddpport.h'
fi
if test -f 'ddprouter.c'
then
	echo shar: "will not over-write existing file 'ddprouter.c'"
else
cat << \SHAR_EOF > 'ddprouter.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:10 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/ddprouter.c,v 1.18 88/09/14 10:19:10 cck Rel $";
static char revision[] = "$Revision: 1.18 $";

/*
 * ddprouter.c - simple ddp router
 *
 * Follows specification set in "Inside Appletalk" by Gursharan Sidhu,
 * Richard F. Andrews, and Alan B. Oppenheimer, Apple Computer, Inc.
 * June 1986.
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>
#include "gw.h"

export void ddp_route_init();
export void ddp_route_start();
export void sddp_route();
export void ddp_route();
export int ddp_open();		/* local ddp routines */
export int ddp_reopen();
private void ddp_input();
export void ddp_output();
private int ddp_matchournode();
private void dump_types();
export void ddp_dump_stats();

#define NUMDDPTYPES 8
char *ddp_type_names[NUMDDPTYPES] = {
  "unknown", "RTMP", "NBP", "ATP", "ECHO", "RTMP Request", "ZIP", "ADSP"
};

#define valid_type(d) (((d)->type>=ddpRTMP&&(d)->type<=ddpADSP)?(d)->type:0)
		       

struct ddp_stats {
  int short_ddp_routed;		/* short ddp sent to router */
  int long_ddp_routed;		/* long ddp sent to router */
  int ddp_forwarded;		/* long ddp forwarded */
  int ddp_types_forwarded[NUMDDPTYPES];	/* breakdown by type */
  int drop_noroute;		/* no routes */
  int drop_hops;		/* dropped because of hops */
  int long_ddp_output;		/* long ddp output */
  int long_ddp_types_output[NUMDDPTYPES]; /* breakdown by type */
  int short_ddp_output;		/* short ddp output */
  int short_ddp_types_output[NUMDDPTYPES];
  int ddp_input;		/* ddp packets input */
  int ddp_types_input[NUMDDPTYPES];
};

private struct ddp_stats ddp_stats;

/* note: open for all ports */
private boolean (*ddp_handlers[ddpMaxSkt+1])(); /* define internal sockets */

/*
 * start ddp routing - need address of an internal router though to allow
 * mpx and aux. services (takes long ddp header, data and datalength )
 *
 * beabridge set to true tells us to tell rtmp to output rtmp packets
 * and to answer rtmp request packets
 *
*/
export void
ddp_route_start()
{
  rtmp_start();
  ddp_svcs_start();		/* start if needed (says we know net) */
}

/*
 * give us a chance to initialize.
 *
*/
export void
ddp_route_init()
{
  int i;

  for (i = 1 ; i < ddpMaxSkt; i++)
    ddp_handlers[i] = NULL;
  rtmp_init();
  ddp_svcs_init();		/* init other ddp services */
}

/*
 * route a short ddp packet coming in from an interface
 * 
 * basically, make the short header into a long ddp header and deliver
 * internally
 *
 * ddps is aligned okay, cannot assume for data
*/
export void
sddp_route(port, srcnode, ddps, data, datalen)
PORT_T port;
byte srcnode;
ShortDDP *ddps;
byte *data;
int datalen;
{
  DDP ddp;
  int i;

  /* get indicated data length */
  i = (ddpLengthMask & ntohs(ddps->length)) - ddpSSize;
  if (i < datalen)		/* reduce in case caller is enet, etc. */
    datalen = i;
  ddp.length = htons(datalen + (ddpSize-ddpSSize));
  ddp.checksum = 0;
  ddp.srcNet = ddp.dstNet = PORT_DDPNET(port);
  ddp.srcNode = srcnode;
  ddp.dstNode = PORT_DDPNODE(port);
  ddp.srcSkt = ddps->srcSkt;
  ddp.dstSkt = ddps->dstSkt;
  ddp.type = ddps->type;
  ddp_stats.short_ddp_routed++;
  ddp_input(port, &ddp, data, datalen);
}

/*
 * route a long ddp packet.
 *
 *
 * ddp is aligned okay, cannot assume for data
*/
export void
ddp_route(port, ddp, data, datalen, isbroadcast)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
int isbroadcast;		/* if port okay, then came in as broadcast */
{
  struct route_entry *re;
  NODE *dstnode;
  int i;

  /* get indicated data length */
  i = (ddpLengthMask & ntohs(ddp->length)) - ddpSize;
  if (i < datalen)		/* reduce in case caller is enet, etc. */
    datalen = i ;

  ddp_stats.long_ddp_routed++;
  /* don't do certain things if the packet comes internally (flagged */
  /* by the fact that the port is null) */
  if (port) {
    /* if from self, then drop */
    if (ddp_matchournode(ddp->srcNet, ddp->srcNode, FALSE)) {
      return;
    }
    /* forward packets for self to self */
    /* don't forward broadcasts 'cept to self*/
    if (ddp->dstNet == PORT_DDPNET(port) &&
	(isbroadcast || (ddp->dstNode == PORT_DDPNODE(port)))) {
      ddp_input(port, ddp, data, datalen);
      return;
    }
  }

  /* get route to send the packet on */
  /* drop if no route or bad port */
  if ((re = route_find(ddp->dstNet)) == NULL) {
    ddp_stats.drop_noroute++;
    return;			/* drop the packet */
  }
  if (PORT_BAD(re->re_port) || !PORT_CONNECTED(re->re_port)) {
    ddp_stats.drop_noroute++;
    return;
  }
  if (re->re_dist == 0) {
    if (ddp->dstNode == PORT_DDPNODE(re->re_port)) {
      ddp_input(re->re_port, ddp, data, datalen);
      return;
    }
    if (ddp->dstNode == DDP_BROADCAST_NODE) /* deliver ourselves a copy */
      ddp_input(re->re_port, ddp, data, datalen);
    if (re->re_ddp_net == 0)	/* drop if no net established yet */
      return;
    dstnode = PORT_MAKENODE(re->re_port, ddp->dstNet, ddp->dstNode);
    ddp_stats.long_ddp_output++;
    ddp_stats.long_ddp_types_output[valid_type(ddp)]++;
  } else {
    /* move over into msg and skip first two bits, but only keep 4 bits */
    int hops = ntohs(ddp->length) >> (8+2) & 0xf;

    if (re->re_ddp_net == 0)	/* drop if no net established yet */
      return;
#define DDP_MAXHOPS 15
#define ddpHopShift 10		/* 10 bits to the left for hops */
    if (hops >= DDP_MAXHOPS) {
      ddp_stats.drop_hops++;
      return;			/* drop it */
    }
    ddp->length = htons((ntohs(ddp->length)&ddpLengthMask)|hops<<ddpHopShift);
    dstnode = re->re_bridgeid_p;
    ddp_stats.ddp_forwarded++;
    ddp_stats.ddp_types_forwarded[valid_type(ddp)]++;
  }
  /* wait until here in case for ourselves (okay) */
  if (re->re_ddp_net == 0) {	/* drop if no net established yet */
    return;
  }
  /* send it out */
  PORT_SEND(re->re_port, dstnode, lapDDP, ddp, ddpSize, data, datalen);
}

/*
 * Internal DDP input/output routines
 *
*/

/*
 * ddp open - open socket for processing - maybe allow socket
 * to be open on a single port only, but not necessary for now
 *
 * Generally, should only be for a very small number of priviledged
 * sockets
*/
export int
ddp_open(ddpskt, handler)
int ddpskt;
boolean (*handler)();
{
  register PORT_T port;

  if (ddpskt > 0 && ddpskt < ddpMaxSkt) {
    /* need to open mpx socket - remember to callback in mpx open too */
    /* how to do ? */
    ddp_handlers[ddpskt] = handler;
    for (port = PORT_LIST_START(); port ; port = PORT_NEXT(port))
      PORT_GRAB(port, ddpskt);
    return(TRUE);
  }
  return(FALSE);
}

/* smash through open socket list and call grab to grab the socket if */
/* necessary (generally for mpx) */
export int
ddp_reopen(grab, arg, carg)
int (*grab)();
int arg;
caddr_t carg;
{
  int i;

  for (i = 1; i < ddpMaxSkt; i++)
    if (ddp_handlers[i])
      (*grab)(i, arg, carg);
}

/* 
 * ddp input.  Processes an packet destined for internal use.
 *
*/
private void
ddp_input(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  int ds;			/* destination socket */

  ddp_stats.ddp_input++;

  /* should we checksum? - probably */
  if (ddp->dstSkt == 0 || ddp->dstSkt == ddpMaxSkt) {
    /* count ddp droped bad type */
    return;
  }

  /* rejct any packet from us (already know from us) */
  /* that is a broadcast and whose srcSkt == dstSkt */
  if (ddp_matchournode(ddp->srcNet, ddp->srcNode, FALSE) &&
      ddp->dstNode == DDP_BROADCAST_NODE &&
      ddp->dstSkt == ddp->srcSkt) {
    /* RECORD */
    return;
  }
  ds = ddp->dstSkt;
  if (ddp_handlers[ds]) {
    /* they return TRUE if they handle, false if we should try to */
    /* forward it to an internal node */
    if ((*(ddp_handlers[ds]))(port, ddp, data, datalen)) {
      ddp_stats.ddp_types_input[valid_type(ddp)]++;
      return;
    }
  }

  /* send to process if naught else */
  if (port)  {
    ddp_stats.ddp_types_input[valid_type(ddp)]++;
    PORT_DEMUX(port, ddp, data, datalen);
  }
}

/*
 * ddp output sends out a packet
 *
 * Special processing:
 *  ddp output will output long ddp only if the appropriate flag is
 *    set and sufficient input is given.
 *  ddp output will also send back a broadcast packet to the input
 *    routines if the "lap" layer is not capable of receiving its own
 *    broadcasts
 *
 * Expect these ddp fields
 *   dstNet
 *   dstNode
 *   dstSkt
 *   srcSkt
 *   type
 * Fills in srcNet, srcNode, length, checksum with appropriate values
 *
 *
*/
export void
ddp_output(dstnode, ddp, data, size)
NODE *dstnode;
DDP *ddp;
byte *data;
int size;
{
  ShortDDP ddps;
  struct route_entry *re;
  PORT_T port;
  byte pddpnode;

  /* make sure we can route to destination */
  if ((re = route_find(ddp->dstNet)) == NULL)
    return;
  port = re->re_port;
  pddpnode = PORT_DDPNODE(re->re_port);
  
  /* short ddp?  */
  /* yes, if dstNet is same as outgoing port */
  /* unless port wants long ddp and node was given */
  /* have "special" case for no node in case it is not possible to map */
  /* from "NODE *" type to a ddp node */
  if (ddp->dstNet == PORT_DDPNET(port) &&
      (dstnode || !PORT_WANTS_LONG_DDP(port))) {
    /* yes */
    ddps.length = htons(ddpSSize + size);
    ddps.dstSkt = ddp->dstSkt;
    ddps.srcSkt = ddp->srcSkt;
    ddps.type = ddp->type;
    if (ddp->dstNode == DDP_BROADCAST_NODE && PORT_NEEDS_BROADCAST(port)) {
      sddp_route(port, pddpnode, &ddps, data, size);
    } else if (ddp->dstNode == pddpnode) {
      sddp_route(port, pddpnode, &ddps, data, size);
      return;
    }
    if (dstnode || (dstnode = PORT_MAKENODE(port, 0, ddp->dstNode))) {
      ddp_stats.short_ddp_output++;
      ddp_stats.short_ddp_types_output[valid_type(ddp)]++;
      PORT_SEND(port, dstnode, lapShortDDP, &ddps, ddpSSize, data, size);
    }
    return;
  }
  /* definitely should put checksum here !!!! */
  ddp->checksum = 0;		/* should we put the checksum in? probably */
  ddp->length = htons(size+ddpSize); /* length */
  ddp->srcNet = PORT_DDPNET(port);
  ddp->srcNode = pddpnode;
  ddp_route(NULL, ddp, (byte *)data, size, FALSE);
}


/*
 * Given a ddp network and node number, ddp matchournode will return true
 * if it corresponds to the ddp network/node number of any port.
 *
*/
private int
ddp_matchournode(net, node, br)
register word net;
register byte node;
int br;
{
  register PORT_T port;

  for (port = PORT_LIST_START() ; port ; port = PORT_NEXT(port))
    if ((net == 0 || PORT_DDPNET(port) == net) &&
	((br && node == DDP_BROADCAST_NODE) || PORT_DDPNODE(port) == node))
      return(TRUE);
  return(FALSE);
}


private void
dump_types(fd, table)
FILE *fd;
int table[];
{
  int i;

  fprintf(fd, "  by type\n");
  for (i = 1; i < NUMDDPTYPES; i++)
    fprintf(fd, "\t%d %s\n", table[i], ddp_type_names[i]);
  /* output "unknown last */
  fprintf(fd, "\t%d %s\n", table[0], ddp_type_names[0]);
}

export void
ddp_dump_stats(fd)
FILE *fd;
{
  fprintf(fd, "\nddp route\n");
  fprintf(fd, " short ddp received: %d\n", ddp_stats.short_ddp_routed);

  fprintf(fd, " long ddp received: %d\n", ddp_stats.long_ddp_routed);
  fprintf(fd, " forwarded to another bridge: %d\n", ddp_stats.ddp_forwarded);
  dump_types(fd, ddp_stats.ddp_types_forwarded);
  fprintf(fd, " output: %d\n", ddp_stats.long_ddp_output);
  dump_types(fd, ddp_stats.long_ddp_types_output);

  fprintf(fd, " ddp packets input for node: %d\n", ddp_stats.ddp_input);
  dump_types(fd, ddp_stats.ddp_types_input);

  fprintf(fd, " short ddp output: %d\n", ddp_stats.short_ddp_output);
  dump_types(fd, ddp_stats.short_ddp_types_output);
  fprintf(fd, " dropped: no route %d\n", ddp_stats.drop_noroute);
  fprintf(fd, "\t   too many hops %d\n", ddp_stats.drop_hops);
}

SHAR_EOF
if test 12559 -ne "`wc -c < 'ddprouter.c'`"
then
	echo shar: "error transmitting 'ddprouter.c'" '(should have been 12559 characters)'
fi
chmod 440 'ddprouter.c'
fi
if test -f 'ddpsvcs.c'
then
	echo shar: "will not over-write existing file 'ddpsvcs.c'"
else
cat << \SHAR_EOF > 'ddpsvcs.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:13 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/ddpsvcs.c,v 1.3 88/09/14 10:19:13 cck Rel $";
static char revision[] = "$Revision: 1.3 $";

/*
 * ddpsvcs.c - simple ddp router
 *
 * Some ddp services
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  September, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <sys/types.h>
#include <netat/appletalk.h>
#include <netat/abnbp.h>

#include "gw.h"

export void ddp_svcs_init();
export void ddp_svcs_start();
private int echo_handler();
private int nbp_handler();
private int nbp_ptrs();
private void nbp_bridge_request_handler();
#ifdef dontdothisrightnow
private void nbp_lookup_handler();
private void nbp_lookup_reply_handler();
export int nbp_lookup();
export int nbp_register();
export int nbp_delete();
export int nbp_tickle();
#endif

/*
 * glue
 *
*/

export void
ddp_svcs_start()
{
}

export void
ddp_svcs_init()
{
  ddp_open(echoSkt, echo_handler);
  ddp_open(nbpNIS, nbp_handler);
}

/*
 * echo service
 *
*/
private boolean
echo_handler(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  byte e_cmd;			/* echo command */
  DDP rddp;			/* reply ddp header */

  if (ddp->type != ddpECHO)	/* dump it */
    return(TRUE);

  e_cmd = *data;			/* first byte is command */
  switch (e_cmd) {
  case echoRequest:
    break;
  case echoReply:
    return(FALSE);		/* still needs forwarding */
  }
  *data = echoReply;		/* establish as reply */
  rddp.dstNet = ddp->srcNet;	/* respond to sender */
  rddp.dstNode = ddp->srcNode;
  rddp.dstSkt = ddp->srcSkt;
  rddp.srcSkt = echoSkt;	/* set socket */
  rddp.type = ddpECHO;		/* and type */
  ddp_output(NULL, &rddp, data, datalen);
  return(TRUE);			/* we handled it */
}

/*
 * handle incoming nbp packet
 *
 *
*/
private boolean
nbp_handler(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  word control;

  if (ddp->type != ddpNBP)	/* dump it */
    return(TRUE);

  if (datalen < nbpMinSize)
    return(TRUE);		/* say we handled it! */
  control = *data >> 4;		/* get nbp control part */
  switch (control) {
  case nbpBrRq:			/* bridge request for lookup */
    nbp_bridge_request_handler(port, ddp, data, datalen);
    return(TRUE);
  case nbpLkUp:			/* nbp lookup */
    break;
  case nbpLkUpReply:		/* nbp lookup reply */
    return(FALSE);		/* for now*/
  /* nbp extended codes */
  case nbpRegister:		/* register a name */
  case nbpDelete:		/* delete a name */
  case nbpTickle:		/* let them know socket is alive and well */
    break;
  case nbpStatusReply:		/* status on register/delete */
  case nbpLookAside:		/* Lookup via NIS */
    return(FALSE);		/* for now */
  }
  return(FALSE);		/* for now */
}

/*
 * given a pointer to an NBP packet, return pointers to the three
 * elements of the nbp entity.  assumes there is only one there.
 *
 * returns the valid length of nbp packet
*/
private int
nbp_ptrs(nbp, obj, typ, zon)
NBP *nbp;
byte **obj;
byte **typ;
byte **zon;
{
  byte *o, *t, *z;
  int ol, tl, zl;
  o = nbp->tuple[0].name;
  ol = *o;
  if (ol >= ENTITYSIZE)
    return(0);
  t = o+ol+1;			/* get ptr to type */
  tl = *t;			/* get type length */
  if (tl >= ENTITYSIZE)
    return(0);
  z = t+tl+1;			/* get ptr to zone */
  zl = *z;
  if (zl >= ENTITYSIZE)
    return(0);
  *obj = o, *typ = t, *zon = z;
  /* add 3 for lengths, add 1 for enum, and addtrblock */
  return(ol+tl+zl+nbpMinSize);
}

/*
 * take an nbp packet and handle it if it is a nbp BrRq
 *
*/
private void
nbp_bridge_request_handler(port, ddp, data, datalen)
PORT_T port;			/* incoming port */
DDP *ddp;
byte *data;
int datalen;
{
  struct route_entry *re;
  int tuplelen;
  byte *obj, *typ, *zon;	/* pointers to parts of entity name */
  byte *zonep;			/* nbp zone to lookup */
  NBP nbp;
  DDP sddp;
  
  bcopy((caddr_t)data,(caddr_t)&nbp,sizeof(NBP)>datalen?datalen:sizeof(NBP));
  nbp.control = nbpLkUp;
  /* if bad tuplelen, then drop */
  if ((tuplelen = nbp_ptrs(&nbp, &obj, &typ, &zon)) <= 0)
    return;
  /* zone of "=" is invalid */
  if (zon[0] == 1 && zon[1] == nbpEquals)
    return;
  /* 0 length zone or "*" qualifies as this zone */
  if (zon[0] == 0 || (zon[0] == 1 && zon[1] == nbpStar)) {
    int t;

    t = zon[0];
    re = route_find(ddp->srcNet == 0 ? PORT_DDPNET(port) : ddp->srcNet);
    if (re == NULL)		/* can't rewrite zone name */
      return;
    if (re->re_zonep == NULL)	/* bad zone? */
      return;
    zonep = re->re_zonep;
    /* copy it in, plenty of space */
    bcopy((caddr_t)zonep, (caddr_t)zon, (int)(1+(*zonep)));
    tuplelen += (*zonep- t); /* fix length */
  } else {
    if ((zonep = zone_find(zon, FALSE)) == NULL)
      return;
  }
  /* fixup and figure out zone */
  sddp = *ddp;
  sddp.checksum = 0;
  sddp.length = htons(tuplelen + ddpSize);
  for (re = route_list_start(); re; re = route_next(re)) {
    if (!re->re_state || re->re_zonep != zonep)
      continue;
    sddp.dstNode = DDP_BROADCAST_NODE;
    sddp.dstNet = re->re_ddp_net;
    log(LOG_LOTS, "nbp lkup net %d.%d for zone %s",
	nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net),
	zonep+1);
    ddp_output(NULL, &sddp, (byte *)&nbp, tuplelen);
  }
  return;
}

#ifdef dontdothisrightnow
/*
 * handle an incoming nbp lookup request
 *
*/
private void
nbp_lookup_handler(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  int tuplelen;
  byte *obj, *typ, *zon;
  NPB nbp;

  bcopy((caddr_t)data,(caddr_t)&nbp,sizeof(NBP)>datalen?datalen:sizeof(NBP));
  /* if bad tuplelen, then drop */
  if ((tuplelen = nbp_ptrs(&nbp, &obj, &typ, &zon)) <= 0)
    return;
  /* zone specified is not myzone */
  if (!(zon[0] == 0 || (zon[0] == 1 && zon[1] == '*'))) {
    /* check to see if zone specified is same as port's zone */
    byte *zp;

    if ((zp = zone_find(zon, FALSE)) == NULL) /* find it */
      return;			/* wasn't there */
    if (zp != port->p_zonep)	/* check the canonical zone name */
      return;			/* bad, return */
  }
  /* check our tables and rspond */
  {
    EntityName *en;
  }
}

private void
nbp_lookup_reply_handler()
{
}

export int
nbp_lookup()
{
}

export int
nbp_register()
{
  /* check by lookup first? */
  /* insert into our tables */
}

export int
nbp_delete()
{
  /* delete from our tables */
}

export int
nbp_tickle()
{
  /* reset timer */
}

#endif /* NOTDONE */
SHAR_EOF
if test 6998 -ne "`wc -c < 'ddpsvcs.c'`"
then
	echo shar: "error transmitting 'ddpsvcs.c'" '(should have been 6998 characters)'
fi
chmod 440 'ddpsvcs.c'
fi
if test -f 'desc.ms'
then
	echo shar: "will not over-write existing file 'desc.ms'"
else
cat << \SHAR_EOF > 'desc.ms'
.\" nroff -ms
.TL
Unix AppleTalk Bridge
.AB
This document describes a
Unix based AppleTalk Bridge 
.I (UAB)
designed to work on a variety of
host unix systems.  UAB also provides for mechanisms to deliver
packets internally.
.AE
.SH
INTRODUCTION
.LP
The Unix AppleTalk
Bridge (UAB) program allows certain unix systems to act as AppleTalk
Bridges.  UAB consists of a number of layers that can have multiple
implementations.  UAB can be functionally divided into two parts.  The
first is the actual AppleTalk Bridge implementation and the second are
the routines that define particular "Link Access Protocols" (aka
"hardware" delivery methods e.g. EtherTalk).  UAB also supports an
internal demultiplexing that allows
packets delivered to the UAB node to be delivered to other processes
within that system.  
.PP
Currently, UAB runs on Ultrix 1.2 (and beyond) and SunOS 4.0 and
supports EtherTalk.  Unfortunately, with the current definition of
KIP's UDP encapsulation and delivery rules, it is not feasible to
implement KIP.
The only internal packet
delivery mechanism defined is a modified version of KIP's UDP
encapsulation (modified-KIP) that uses a different UDP port range over
the internal
loopback; thus CAP programs must be relinked with a different low
level delivery mechanism to work with UAB.  Note that all packets for
these programs are sent and received through the UAB process.
Since UAB does not understand KIP, 
it is necessary to have an AppleTalk Bridge that
understands both KIP encapsulation and EtherTalk before KIP based
"systems" (e.g. programs compiled with CAP, bridges that only speak
KIP on the ethernet interface--revisions of KIP before 2/88, etc) can
work with UAB modified-KIP based programs.
.SH
Definitions
.LP
.IP
An 
.I interface
defines the
underlying delivery protocol.  The only delivery protocol supported at
the present time is EtherTalk.
.IP
A
.I port
abstracts an interface into a workable DDP entity.  DDP level
functions deal with ports rather than interfaces.  A port carries
information such as interface input/output mechanisms, ddp network
numbers, etc.
.IP
The
.I port manager
is a set of routines that handle ports.  Only the port manager
directly manipulates a port.  Both the lap layer and the ddp layer
call the port manager.
.IP
A
.I node
is a DDP/RTMP concept that defines nodes in a way that should contain
all the various LAP definitions.  In particular, a node is defined as the
tuple <# of bits, bits> where the number of bits can be between 1 and
255.  This is more general than the original LocalTalk LAP definition
which defines a node as 8 bits.
.SH
AppleTalk Bridge
.LP
UAB builds upon the concepts of 
.I interface,
.I port,
and
.I node
to separate itself from the underlying delivery mechanism.
As an AppleTalk bridge, it provides full RTMP and ZIP services as
defined in Inside AppleTalk.  In addition, it provides the NBP Bridge
Lookup services.
.PP
As all AppleTalk bridges, it is also a node on the various AppleTalk
networks to which it is directly connected.  Packets directed to its node
number (e.g. that aren't supposed to be forwarded) and which are not
directly related to bridge management (RTMP, ZIP, and NBP BrLk) are
handled in two ways.  The first provides a simple "port" wide
services: when the socket is "opened" it is opened for all known and
future ports.  The only one currently defined is DDP ECHO.  In the
future, it may be necessary or advisable to add other NBP services
such as outgoing lookup, internal name management, etc; however, that
has not yet been done.  For "unopened" sockets the packets are
sent to a "demultiplexer".  (NBP is considered "partially" opened for
our purposes-the handler only picks out the bridge lookup packets).
.PP
The demultiplexor/multiplexor is supposed to solve the problems of
sending to other
processes on the system (if the system is processes based like unix).
There are a number of requirements associated with the demultiplexor under
Unix.  First, the demultiplexor delivery mechanism does not have to be 
reliable since it is delivering ddp packets: since DDP is considered
unreliable, there must be higher level policies that ensure delivery.
Second, the demultiplexing end must be able to send DDP packets to the
correct processes
in a way that the processes can decode what the DDP socket number was.
For example, with UDP, it is simple enough to define a port range and
send the packet to a particular port: if a program is listening on it,
it will receive it and know exactly which socket (based upon a
mapping) it was meant for.  With UDP, the process knows that
a stronger condition holds
because the processes knows apriori what the DDP socket number is and
can do different reads based upon this (e.g. customized io vectors).
Third, the multiplexing end must be able to know the DDP socket that the
process is sending to.  With UDP, the best way is to have the
multiplexing end listen to a single socket: the recv call can return
the source port number (which then can be translated in to the DDP
socket).  Fourth, both sides must be relatively sure of the
"trustworthiness" of the packets: e.g. one must not be able to have
"untrustworthy" agents intercept or inject packets undetectably.
Fifth, it is necessary that the mechanism work within reasonable
implementation boundaries.  For instances, a mechanism that required
the full DDP range of 254 sockets to be opened (e.g have that many
file descriptors/open files) would not fit within
those requirements upon most if not all of today's unix systems.
.PP
The only mechanism defined so far that allows these requirements to be
fulfilled in a reasonable fashion is the modified KIP scheme, but even
there, the security requirement has been loosened.  The primary reason
that it works well is that one can define a single point of contact on
the demux/mux process that goes to many points (on many processes)
within the constraints mentioned above.  Basically,
it's real easy to use UDP because it allows one to use the
kernel to do the fan-in and fan-out
functionality.
.SH
Link Access Protocols/Interfaces
.LP
.I UAB
uses DDP ports and interfaces to abstract the bridge functionality
from the delivery mechanisms.  The level of separation is at the ddp
layer.  As defined before, an interface is a basic description of a
particular delivery protocol such as EtherTalk (ELAP, implemented) or
LocalTalk (ALAP, not implemented at present).
When initialized, an interface sends information to the ddp port
manager that defines its basic operating characteristics.
.PP
The only delivery protocol defined at present is the EtherTalk Link
Access Protocol (aka EtherTalk).  Other delivery protocols may be
defined for other systems with particular hardware (e.g. Mac II
running A/UX with a localtalk card) in the future or by other parties.
The SunOS and Ultrix ELAPs are implemented on top of a specialized
facility available on both (in different forms) that allow "opening"
an Ethernet protocol: all packets addressed to that host with a
particular protocol type are delivered to the UAB process.  No or very
little processing is done by the kernel.  To complete these ELAP, AARP
is also implemented.  The only protocol interface library
implemented under SunOS is based upon the streams version of the
Network Interface Tap first made available in SunOS 4.0.  The protocol
interface library for Ultrix is based on the Data Link Inteface
facility (c.f. DECNET documentation).
.PP
The ELAP implementation is abstracted from the actual "ethernet
protocol" facilities by the use of a set of "protocol interface
routines" (poor choice of names, but was made a long time ago when the
routines were meant for a far different purpose).
SHAR_EOF
if test 7722 -ne "`wc -c < 'desc.ms'`"
then
	echo shar: "error transmitting 'desc.ms'" '(should have been 7722 characters)'
fi
chmod 440 'desc.ms'
fi
if test -f 'dlip.c'
then
	echo shar: "will not over-write existing file 'dlip.c'"
else
cat << \SHAR_EOF > 'dlip.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:20 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/dlip.c,v 1.11 88/09/14 10:19:20 cck Rel $";
static char revision[] = "$Revision: 1.11 $";

/*
 * dlip.c - Simple "protocol" level interface to DLI
 *
 *  Provides ability to read/write packets at ethernet level
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  April 3, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netdnet/dli_var.h>

#include <netat/appletalk.h>
#include "proto_intf.h"

typedef struct ephandle {	/* ethernet protocol driver handle */
  int inuse;			/* true if inuse */
  int socket;			/* file descriptor of socket */
  int protocol;			/* ethernet protocol */
  struct sockaddr_dl sdli;	/* dli interface: to send with */
  struct sockaddr_dl rdli;	/* dli interface: to receive with */
} EPHANDLE;

private inited = FALSE;

private EPHANDLE ephlist[MAXOPENPROT];

/*
 * setup for particular device devno
 * all pi_open's will go this device
 *
*/
export
pi_setup()
{
  int i;

  if (!inited) {
    for (i = 0 ; i < MAXOPENPROT; i++)
      ephlist[i].inuse = FALSE;
    (void)init_fdlistening();
    inited = TRUE;		/* don't forget now */
  }
}

/*
 * Open up a protocol handle:
 *   user level data:
 *      file descriptor
 *      protocol
 * 
 *   returns -1 and ephandle == NULL if memory allocation problems
 *   returns -1 for other errors
 *   return 0 for okay
*/
export int
pi_open(protocol, dev, devno)
int protocol;
char *dev;
int devno;
{
  struct ephandle *eph;
  struct sockaddr_dl *dl;
  int s;
  int i;

  for (i = 0; i < MAXOPENPROT; i++) {
    if (!ephlist[i].inuse)
      break;
  }
  if (i == MAXOPENPROT)
    return(0);			/* nothing */
  eph = &ephlist[i];		/* find handle */

  dl = &eph->sdli;		/* point */
  dl->dli_family = AF_DLI;
  strcpy(dl->dli_device.dli_devname, dev);
  dl->dli_device.dli_devnumber = devno;
  dl->dli_substructype = DLI_ETHERNET;
  /*  update these */
  dl->choose_addr.dli_eaddr.dli_ioctlflg = DLI_EXCLUSIVE;
  dl->choose_addr.dli_eaddr.dli_protype = protocol;

  if ((s = socket(AF_DLI, SOCK_DGRAM, DLPROTO_DLI)) < 0)
    return(-1);
  if (bind(s, dl, sizeof(struct sockaddr_dl)) < 0) {
    close(s);
    return(-1);
  }
  bcopy(dl, &eph->rdli, sizeof(struct sockaddr_dl));

  eph->inuse = TRUE;
  eph->socket = s;
  eph->protocol = protocol;
  return(i+1);			/* skip zero */
}

/* returns TRUE if machine will see own broadcasts */
export int
pi_delivers_self_broadcasts()
{
  return(TRUE);
}

export int
pi_close(edx)
int edx;
{
  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);
  fdunlisten(ephlist[edx-1].socket); /* toss listener */
  close(ephlist[edx-1].socket);
  ephlist[edx-1].inuse = 0;
  return(0);
}

export int
pi_get_ethernet_address(edx,ea)
int edx;
u_char *ea;
{
  struct ifdevea buf;
  struct ephandle *eph;

  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);

  eph = &ephlist[edx-1];		/* find handle */
  sprintf(buf.ifr_name, "%s%d",eph->sdli.dli_device.dli_devname,
	  eph->sdli.dli_device.dli_devnumber);
  if (ioctl(eph->socket,SIOCRPHYSADDR, &buf) < 0) {
    perror("iotcl");
    return(-1);
  }
  bcopy(buf.current_pa, ea, DLI_EADDRSIZE);
  return(0);
}

export
pi_listener(edx, listener, arg)
int edx;
int (*listener)();
caddr_t arg;
{
  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);

  fdlistener(ephlist[edx-1].socket, listener, arg, edx);
  return(0);
}


export int
pi_readv(edx, iov, iovlen)
int edx;
struct iovec *iov;
int iovlen;
{
  struct msghdr msg;
  int cc;
  struct ephandle *eph ;
  struct ethernet_addresses *ea;

  if (edx < 1 || edx > MAXOPENPROT)
    return(-1);
  eph = &ephlist[edx-1];
  if (!eph->inuse)
    return(-1);

  msg.msg_iov = iov+1;
  msg.msg_iovlen = iovlen-1;
  msg.msg_name = (caddr_t)&eph->rdli;
  msg.msg_namelen = sizeof(eph->rdli);
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((cc = recvmsg(eph->socket, &msg, 0)) < 0) {
    perror("recvmsg");
    return(cc);
  }
  ea = (struct ethernet_addresses *)iov[0].iov_base;
  ea->etype = eph->protocol;
  /* check length -- naw */
  bcopy(eph->rdli.choose_addr.dli_eaddr.dli_target, ea->saddr, EHRD);
  bcopy(eph->rdli.choose_addr.dli_eaddr.dli_dest, ea->daddr, EHRD);
  return(cc+iov[0].iov_len);
}

export int
pi_read(edx, buf, bufsiz)
int edx;
caddr_t buf;
int bufsiz;
{
  struct iovec iov[2];
  struct ethernet_addresses ea;
  int cc;

  iov[0].iov_base = (caddr_t)&ea;
  iov[0].iov_len = sizeof(ea);
  iov[1].iov_base = (caddr_t)buf;
  iov[1].iov_len = bufsiz;
  cc = pi_readv(edx, iov, 2);
  return(cc - sizeof(ea));
}

export int
pi_write(edx, buf, buflen, eaddr)
int edx;
caddr_t buf;
int buflen;
char *eaddr;
{
  struct iovec iov[1];

  iov[0].iov_base = buf;
  iov[0].iov_len = buflen;
  return(pi_writev(edx, iov, 1, eaddr));
}

export int
pi_writev(edx, iov, iovlen, eaddr)
int edx;
struct iovec *iov;
int iovlen;
char *eaddr;
{
  struct ephandle *eph;
  struct msghdr msg;
  int cc;

  if (edx < 1 || edx > MAXOPENPROT || eaddr == NULL)
    return(-1);
  eph = &ephlist[edx-1];
  if (!eph->inuse)
    return(-1);

  bcopy(eaddr, eph->sdli.choose_addr.dli_eaddr.dli_target, DLI_EADDRSIZE);
  msg.msg_name = (caddr_t)&eph->sdli;
  msg.msg_namelen = sizeof(eph->sdli);
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  msg.msg_iov = iov;
  msg.msg_iovlen = iovlen;
  cc = sendmsg(eph->socket, &msg, 0);
  return(cc);
}
SHAR_EOF
if test 6243 -ne "`wc -c < 'dlip.c'`"
then
	echo shar: "error transmitting 'dlip.c'" '(should have been 6243 characters)'
fi
chmod 440 'dlip.c'
fi
if test -f 'ethertalk.c'
then
	echo shar: "will not over-write existing file 'ethertalk.c'"
else
cat << \SHAR_EOF > 'ethertalk.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:26 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/ethertalk.c,v 1.16 88/09/14 10:19:26 cck Rel $";
static char revision[] = "$Revision: 1.16 $";

/*
 * ethertalk.c - ethertalk interface
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  April 3, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>

#include "proto_intf.h"		/* iso: level 0 */
#include "ethertalk.h"		/* iso: level 1 */
#include "aarp.h"		/* iso: level 1 */

#include "if_desc.h"		/* describes "if" */
#include "ddpport.h"		/* describes a ddp port to "lap" */
#include "log.h"

/* some logging ideas */
#define LOG_LOG 0
#define LOG_PRIMARY 1
#define LOG_LOTS 9

/*
   Ethertalk packet format:
   :: destination node ::
   :: source node ::
   <data>

*/

private int etalk_init();
private int etalk_getnode();	/* basis */
private etalk_initfinish();

private int etalk_send_ddp();
private int etalk_listener();
private NODE *etalk_ddpnode_to_node();
private int etalk_stats();
private int etalk_tables();

/* describe our interface to the world */
private char *ethertalk_lap_keywords[] = {
  "ethertalk",
  "elap",
  NULL
 };

export struct lap_description ethertalk_lap_description = {
  "EtherTalk Link Access Protocol",
  ethertalk_lap_keywords,
  TRUE,				/* need more than just key */
  etalk_init,			/* init routine */
  etalk_stats,			/* stats routine */
  etalk_tables			/* tables */
};


/* interface statistics */

private char *estat_names[] = {
#define ES_PKT_INPUT  0		/* packets input */
  "packets input",
#define ES_PKT_ACCEPTED	1	/* accepted input packets */
  "packets accepted",
#define ES_PKT_BAD 2		/* bad packets */
  "bad packets",
#define ES_PKT_NOTFORME 3	/* not for me packets */
  "packets not for me",
#define ES_BYTES_INPUT 4	/* accepted bytes */
  "bytes input",
#define ES_ERR_INPUT 5 		/* number of input errors */
  "input errors",
#define ES_PKT_NOHANDLER 6	/* no handler */
  "packets without handlers",
#define ES_PKT_OUTPUT  7	/* packets output */
  "packets output",
#define ES_BYTES_OUTPUT 8	/* bytes output */
  "bytes output",
#define ES_ERR_OUTPUT 9		/* output errors */
  "output errors",
#define ES_RESOLVE_ERR_OUTPUT 10 /* could not resolvve */
  "output resolve error"
#define ES_NUM_COUNTERS 11
};

typedef struct ethertalk_handle {
  int eh_state;			/* this connection state */
#define ELAP_WAITING -1
#define ELAP_BAD 0		/* error */
#define ELAP_READY 1		/* okay */
  PORT_T eh_port;		/* ethertalk port */
  int eh_ph;			/* ethertalk protocol handle */
  caddr_t eh_ah;		/* aarp module handle */
  NODE eh_enode;		/* host node id */
  IDESC_TYPE *eh_id;		/* interface description */
  int eh_stats[ES_NUM_COUNTERS]; /* statistics */
} E_HANDLE;


/*
 * call with provisional network number, interface name and number
 *
 * provisional number should be 0 if not seeding
 *
*/
private int
etalk_init(id, async)
IDESC_TYPE *id;
int async;
{
  int etph;
  E_HANDLE *eh;
  int hostid;


  if ((eh = (E_HANDLE *)malloc(sizeof(E_HANDLE))) == NULL)
    return(NULL);

  pi_setup();

  if ((etph = pi_open(ETHERTYPE_APPLETALK, id->id_intf, id->id_intfno)) < 0) {
    log(LOG_LOG|L_UERR,"pi_open");
    free(eh);
    return(NULL);
  }
  eh->eh_ph = etph;
 
  /* init for a single node */
  eh->eh_ah = (caddr_t)aarp_init(id->id_intf, id->id_intfno, 1);
  if (eh->eh_ah == NULL) {
    log(LOG_LOG|L_UERR, "aarp_init");
    pi_close(etph);
    free(eh);
    return(NULL);
  }
  /* link in both directions */
  id->id_ifuse = (caddr_t)eh;	/* mark */
  eh->eh_id = id;		/* remember this */

  eh->eh_state = ELAP_WAITING;	/* mark waiting */

  /* acquire node address */
  hostid = 0xff & gethostid();	/* use last byte of hostid as hint */
  if (etalk_getnode(eh, hostid, etalk_initfinish) < 0) {
    pi_close(etph);
    free(eh);
  }

  if (async)			/* async means to stop early */
    return(TRUE);
  /* wait for node acquisition? */
  while (eh->eh_state == ELAP_WAITING)
    abSleep(10, TRUE);
  return(eh->eh_state == ELAP_READY); /* true if okay, 0 o.w. */
}

/*
 * try to acquire an ethertalk host node address using hint as the basis
 *  callback to who (cbarg is E_HANDLE, result where -1 if address in use
 *  host node address index o.w.)
 *
*/
private int
etalk_getnode(eh, hint, who)
E_HANDLE *eh;
int hint;
int (*who)();
{
  struct ethertalkaddr pa;
  int n;

  pa.dummy[0] = pa.dummy[1] = pa.dummy[2] = 0;
  /* EtherTalk II fixup */
  pa.node = hint;		/* use fourth byte of eaddr as guess */
  while ((n=aarp_acquire_etalk_node(eh->eh_ah, &pa, who, eh)) != 0) {
    if (n < 0) {
      /* error */
      /* clean up */
      return(-1);
    }
    pa.node++;			/* try next */
  }
  return(0);
}

/*
 * finish off the init
 *
*/
private
etalk_initfinish(eh, result)
E_HANDLE *eh;
int result;
{
  PORT_T eh_port;		/* ethertalk port */
  struct ethertalkaddr pa;
  int flags;
  int nodesize;
  IDESC_TYPE *id = eh->eh_id;	/* get interface description */

  if (result < 0) {
    if ((result = etalk_getnode(eh,(rand()%254)+1, etalk_initfinish)) < 0) {
      log(LOG_LOG, "could not acquire node on interface %s%d\n",
	  id->id_intf, id->id_intfno);
      eh->eh_state = ELAP_BAD;
    }
    return;
  }

  if ((nodesize = aarp_get_host_addr(eh->eh_ah, &pa, result)) < 0) { 
    log(LOG_PRIMARY, "aarp get host node address failed for %d", result);
    log(LOG_PRIMARY, "interface %s%d can't be intialized",
	id->id_intf, id->id_intfno);
    eh->eh_state = ELAP_BAD;	/* mark bad */
    return;
  }
  eh->eh_enode.n_size = 8*nodesize; /* 8 bits */
  eh->eh_enode.n_bytes = nodesize; /* 1 byte */
  /* EtherTalk II fixup */
  eh->eh_enode.n_id[0] = pa.node; /* this is it */

  flags = PORT_WANTSLONGDDP;
  if (!pi_delivers_self_broadcasts())
    flags |= PORT_NEEDSBROADCAST;
  if (id->id_isabridge)
    flags |= PORT_FULLRTMP;

  /* establish port */
  /* EtherTalk II fixup */
  eh_port = port_create(id->id_network, pa.node, id->id_zone,
			&eh->eh_enode, flags, (caddr_t)eh,
			etalk_send_ddp, /* send interface */
			etalk_ddpnode_to_node, /* map from ddp */
			NULL,	/* map node to ddp node, net */
			id->id_local);	/* demuxer */
  if (eh_port) {
    /* go to ethertalk level */
    pi_listener(eh->eh_ph, etalk_listener, (caddr_t)eh_port);
    eh->eh_state = ELAP_READY;
    log(LOG_PRIMARY, "port %d acquired node %d on interface %s%d",
	eh_port, pa.node, id->id_intf, id->id_intfno);
  } else {
    eh->eh_state = ELAP_BAD;
   log(LOG_PRIMARY,"acquired node %d on interface %s%d, but no space for port",
	pa.node, id->id_intf, id->id_intfno);
  }
  /* phew */
}

/*
 * listen to incoming ethertalk packets and handle them 
 *
*/
/*ARGSUSED*/
private
etalk_listener(fd, port, etph)
int fd;				/* dummy */
PORT_T port;
int etph;
{
  static LAP lap;
  /* room for packet and then some */
  static byte rbuf[ddpMaxData+ddpSize+lapSize+100];
  int cc;
  struct iovec iov[3];
  struct ethertalkaddr spa;
  struct ethernet_addresses ea;
  struct ethertalk_handle *eh = PORT_GETLOCAL(port, struct ethertalk_handle *);
  int *stats = eh->eh_stats;
  int ddpnode;

  iov[0].iov_base = (caddr_t)&ea;
  iov[0].iov_len = sizeof(ea);
  iov[1].iov_base = (caddr_t)&lap;
  iov[1].iov_len = sizeof(lap);
  iov[2].iov_base = (caddr_t)rbuf;
  iov[2].iov_len = sizeof(rbuf);
  if ((cc = pi_readv(etph, iov, 3)) < 0) {
    log(LOG_LOG|L_UERR, "pi_readv: ethertalk_listener");
    stats[ES_ERR_INPUT]++;		/* input error */
    return(cc);
  }
  /* eat the packet and drop it */
  if (eh->eh_state != ELAP_READY) /* drop */
    return(cc);
  /* handle packet */
  cc -= (lapSize+sizeof(ea));

  if (lap.src == 0xff) {		/* bad, bad, bad */
    stats[ES_PKT_BAD]++;
    return(-1);
  }
  /* lap dest isn't right */
  /* fixup point */
  ddpnode = PORT_DDPNODE(port);
  if (lap.dst != 0xff && lap.dst != ddpnode) {
    stats[ES_PKT_NOTFORME]++;
    return(-1);
  }

  stats[ES_PKT_INPUT]++;
  stats[ES_BYTES_INPUT] += cc;
  /* pick out source for aarp table management if not self */
  /* EtherTalk II fixup */
  if (lap.src != ddpnode) {
    spa.dummy[0] = spa.dummy[1] = spa.dummy[2] = 0;
    spa.node = lap.src;
    if (!aarp_insert(eh->eh_ah, ea.saddr, &spa, FALSE)) /* drop it */
      return(-1);		/* enet address change */
  }
  
  switch (lap.type) {
  case lapDDP:
    ddp_route(port, rbuf, rbuf+ddpSize, cc, lap.dst == 0xff);
    break;
  case lapShortDDP:		/* don't allow short ddp for now */
    /*munge short ddp to ddp */
    sddp_route(port, lap.src, rbuf, rbuf+ddpSSize, cc);
    break;
  default:
    stats[ES_PKT_NOHANDLER]++;
    return(-1);
  }
  return(0);
}

/*
 * resolve a ddp node number to a node address on the specified port
 * (note: do we need more information in some cases?)
*/
private NODE *
etalk_ddpnode_to_node(port, ddpnet, ddpnode)
PORT_T port;
word ddpnet;
byte ddpnode;
{
  /* EtherTalk II fixup */
  /* think this is okay */
  static NODE node = { 1, 8 }; /* initialize */
  int myddpnet = PORT_DDPNET(port);

  if (ddpnet != 0 && myddpnet != ddpnet) /* only allow this net! */
    return(NULL);
  node.n_id[0] = ddpnode;	/* make node */
  return(&node);
}

/* resolve a node to a ddp node (do we want this?) */
/* think we will need it + one that resolves it to a net in the future ? */
private byte
etalk_node_to_ddpnode(port, node)
PORT_T port;
NODE *node;
{
  /* EtherTalk II fixup */
  if (node->n_size == 8)	/* 8 bits? */
    return(node->n_id[0]);
  return(0);
}


/*
 * send a ddp packet on ethertalk
 * (should we convert short to long ddp?)
 * 
 * port = port to send on
 * dstnode == destination ethertalk node
 * laptype == laptype of packet (header)
 * header = packet header (for laptype)
 * hsize = packet header length
 * data = data
 * dlen = datalength
*/
private
etalk_send_ddp(port, dstnode, laptype, header, hsize, data, dlen)
PORT_T port;
NODE *dstnode;
int laptype;
byte *header;
int hsize;
u_char *data;
int dlen;
{
  struct iovec iov[3];
  u_char *eaddr;
  LAP lap;
  struct ethertalk_handle *eh = PORT_GETLOCAL(port, struct ethertalk_handle *);
  struct ethertalkaddr tpa;
  int *stats = eh->eh_stats;
  int i;

  if (eh->eh_state != ELAP_READY) { /* drop */
    stats[ES_ERR_OUTPUT]++;
    return(-1);
  }
  if (dstnode == NULL) {	/* can't! */
    stats[ES_ERR_OUTPUT]++;		/* can't */
    return(-1);
  }

  /* should be higher? */
  if (dstnode->n_size != eh->eh_enode.n_size) { /* for now? */
    stats[ES_ERR_OUTPUT]++;		/* can't */
    return(-1);
  }
  /* source is always us! */
  lap.src = eh->eh_enode.n_id[0]; /* get source node */
  lap.dst = dstnode->n_id[0];	/* get dest node */
  lap.type = laptype;
  /* EtherTalk II fixup */
  tpa.dummy[0] = tpa.dummy[1] = tpa.dummy[2] = 0;
  tpa.node = dstnode->n_id[0];

  if (aarp_resolve(eh->eh_ah, &tpa, lap.dst == 0xff, &eaddr) <= 0) {
    stats[ES_RESOLVE_ERR_OUTPUT]++;
    return(-1);
  }
  iov[0].iov_len = lapSize;
  iov[0].iov_base = (caddr_t)&lap;
  iov[1].iov_len = hsize;
  iov[1].iov_base = (caddr_t)header;
  iov[2].iov_len = dlen;
  iov[2].iov_base = (caddr_t)data;
  if ((i = pi_writev(eh->eh_ph, iov, (dlen == 0) ? 2 : 3, eaddr)) < 0) {
    stats[ES_ERR_OUTPUT]++;
    return(i);
  }
  stats[ES_PKT_OUTPUT]++;
  stats[ES_BYTES_OUTPUT] += i;
  return(i);
}


private int
etalk_stats(fd, id)
FILE *fd;
IDESC_TYPE *id;
{
  E_HANDLE *eh = (E_HANDLE *)id->id_ifuse; /* get handle */
  int i;
  
  fprintf(fd, "Interface %s%d statisitics\n", id->id_intf,
	  id->id_intfno);
  fprintf(fd, " Interface counters\n");
  for (i = 0; i < ES_NUM_COUNTERS; i++) {
    fprintf(fd, "  %8d\t%s\n", eh->eh_stats[i], estat_names[i]);
  }
  putc('\n', fd);		/* carriage return */
  /* call up aarp too */
  aarp_dump_stats(fd, eh->eh_ah);
  putc('\n', fd);		/* finish */
}

private int
etalk_tables(fd, id)
FILE *fd;
IDESC_TYPE *id;
{
  E_HANDLE *eh = (E_HANDLE *)id->id_ifuse; /* get handle */

  fprintf(fd, "Interface dump for %s%d\n",id->id_intf, id->id_intfno);
  aarp_dump_tables(fd, eh->eh_ah);
  putc('\n', fd);
}
SHAR_EOF
if test 12726 -ne "`wc -c < 'ethertalk.c'`"
then
	echo shar: "error transmitting 'ethertalk.c'" '(should have been 12726 characters)'
fi
chmod 440 'ethertalk.c'
fi
if test -f 'ethertalk.h'
then
	echo shar: "will not over-write existing file 'ethertalk.h'"
else
cat << \SHAR_EOF > 'ethertalk.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:32 $
 * $Header: /src/local/mac/cap/etalk/RCS/ethertalk.h,v 1.5 88/09/14 10:19:32 cck Rel $
 * $Revision: 1.5 $
*/

/*
 * Ethertalk definitions
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Edit History:
 *
 *  August 1988  CCKim Created
 *
*/

/* format of an ethertalk appletalk address */
struct ethertalkaddr {
  byte dummy[3];		/* should be "network" */
  byte node;			/* appletalk node # */
};

#ifndef ETHERTYPE_APPLETALK
# define ETHERTYPE_APPLETALK 0x809b
#endif

#define ETPL 4			/* ethertalk protocol address length */

SHAR_EOF
if test 1041 -ne "`wc -c < 'ethertalk.h'`"
then
	echo shar: "error transmitting 'ethertalk.h'" '(should have been 1041 characters)'
fi
chmod 440 'ethertalk.h'
fi
if test -f 'gw.h'
then
	echo shar: "will not over-write existing file 'gw.h'"
else
cat << \SHAR_EOF > 'gw.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:34 $
 * $Header: /src/local/mac/cap/etalk/RCS/gw.h,v 1.14 88/09/14 10:19:34 cck Rel $
 * $Revision: 1.14 $
*/

/*
 * gw.h - common definitions for our bridge
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August, 1988  CCKim Created
 *
*/

#include "node.h"
#include "ddpport.h"

/* probably shouldn't be defined here */
#ifndef DDP_BROADCAST_NODE
# define DDP_BROADCAST_NODE 0xff /* broadcast ddp node */
#endif

/*
 * a route entry describes a routing to a ddp network
 *
 * Most of the items are taken pretty much from inside appletalk.
 * 
 * The bridge "lap" id is stored as a pointer to a node address to
 * minimize the comparision overhead (though it produces a consistency
 * and lookup efficiency problem)--with a pointer we need only compare
 * two addreses.
 *
*/
struct route_entry {
  struct route_entry *re_next;	/* next route entry */
  PORT_T re_port;		/* port network is attached to */
  NODE *re_bridgeid_p;		/* bridge (lap level id) pointer */
  word re_ddp_net;		/* network we are routing */
  int re_dist;			/* distance (hops for now) */
  int re_state;			/* state of network */
  byte *re_zonep;		/* zone pascal string (NULL means none) */
  int re_zip_taken;		/* zip takedown in effect */
  int re_zip_helper;		/* just a helper var */
};

/* log levels */
#define LOG_LOG 0		/* always */
#define LOG_PRIMARY 1		/* primary information */
#define LOG_BASE 4		/* base info */
#define LOG_LOTS 6		/* lots of good info */
#define LOG_JUNK 8		/* starting to get into junk here */

/* function declarations */
/* rtmp.c */
void rtmp_init();
void rtmp_start();

struct route_entry *route_find();
struct route_entry *route_list_start();
struct route_entry *route_next();
int route_add_host_entry();

char *node_format();

byte *zone_find();

void rtmp_dump_stats();
void rtmp_dump_table();


/* ethertalk.c */
PORT_T ethertalk_init();

/* ddproute.c */
void ddp_route_init();
void ddp_route_start();
void sddp_route();
void ddp_route();
int ddp_open();		/* local ddp routines (input via listener) */
void ddp_output();
void ddp_dump_stats();

SHAR_EOF
if test 2594 -ne "`wc -c < 'gw.h'`"
then
	echo shar: "error transmitting 'gw.h'" '(should have been 2594 characters)'
fi
chmod 440 'gw.h'
fi
if test -f 'hash.3'
then
	echo shar: "will not over-write existing file 'hash.3'"
else
cat << \SHAR_EOF > 'hash.3'
.TH hash 3 
.SH NAME
h_new, h_operation, h_free, h_redefine\- manage hash tables
.SH SYNTAX
.B #include <sys/types.h>
.br
.B #include <hash.h>
.PP
.B "caddr_t h_new(policy, htype, M, compare, allocate, compress, \
hash, shash, chainops)"
.br
.B int policy;
.br
.B int htype;
.br
.B int M;
.br
.B int (\(**compare)();
.br
.B caddr_t (\(**allocate)();
.br
.B u_int (\(**compress)();
.br
.B u_int (\(**hash)();
.br
.B u_int (\(**shash)();
.br
.B struct hash_bucket_list_ops \(**chainops;
.PP
.B "int (\(**compare)(key, data)"
.br
.B caddr_t key;
.br
.B caddr_t data;
.PP
.B "caddr_t (\(**allocate)(p)"
.br
.B caddr_t p;
.PP
.B u_int (\(**hash)(M, logM, item);
.br 
.B int M;
.br
.B int logM;
.br
.B caddr_t item;
.PP
.B u_int (\(**shash)(M, logM, hidx, item);
.br
.B int M;
.br
.B int logM;
.br
.B int hidx;
.br
.B caddr_t item;
.PP
.B u_int (\(**compress)(item);
.br
.B caddr_t item;
.PP
.B "caddr_t h_operation(operation, hth, key, bkt, dadvance, distance, bucket)"
.br
.B int operation;
.br
.B caddr_t hth;
.br
.B caddr_t key;
.br
.B int bkt;
.br
.B int dadvance;
.br
.B int \(**distance;
.br
.B int \(**bucket;
.PP
.B MACRO on h_operation:
.br
.B h_member(hth,key)
.br
.B caddr_t hth;
.br
.B caddr_t key;
.PP
.B MACRO on h_operation:
.br
.B h_insert(hth, key)
.br
.B caddr_t hth;
.br
.B caddr_t key;
.PP
.B MACRO on h_operation:
.br
.B h_delete(hth,key)
.br
.B caddr_t hth;
.br
.B caddr_t key;
.PP
.B "caddr_t h_redefine(hth, policy, htype, M, compare, allocate, \
hash, shash, compress, chainops)"
.br
.B caddr_t hth;
.br
.B int policy;
.br
.B int htype;
.br
.B int M;
.br
.B int (\(**compare)();
.br
.B caddr_t (\(**allocate)();
.br
.B caddr_t (\(**compress)();
.br
.B u_int (\(**hash)();
.br
.B u_int (\(**shash)();
.br
.B struct hash_bucket_list_ops \(**chainops;
.PP
.B MACRO on h_redefine:
.br
.B h_rehash(hth,M)
.br
.B caddr_t hth;
.br
.B int M;
.PP
.B "void h_free(hth, free_func)"
.br
.B caddr_t hth;
.br
.B int (\(**free_func)();
.PP
.B int (\(**free_func)(data);
.br
.B caddr_t data
.PP
.B "struct hash_statistics *h_statistics(hth)"
.br
.B caddr_t hth;
.SH DESCRIPTION
.I h_new,
.I h_redefine,
.I h_free,
and
.I h_operation
define a general purpose hash table manager that is capable of
handling collision resolution via chaining and open hashing with
linear probing and double probing.
.PP
.I h_new
is used to create and define the parameters for a hash table.
.I h_redefine
allows you to redefine the hash table parameters.  The
associated macro
.I h_rehash
allows you redefine the size of the table.
.I h_free
is used to free a hash table.
.PP
.I h_operation
provides "member", "insert", and "delete" functions for a hash table.
h_operation provides a high degree of control to the user.  There are
three associated macros
.I h_insert,
.I h_delete,
and
.I h_member
that act as "wrappers" to
.I h_operation
for
simple operation.
.SH Creating hash tables
.I h_new
creates a new hash table and returns a handle that is used to
reference it.  The various arguments to h_new define the hash table
definition (e.g. chaining, open hash, etc) and define some general
functions necessary for the hashing operations (insert, delete, find).
.PP
.I policy
defines how collisions are to be resolved.
.I HASH_POLICY_CHAIN
says that we should chain off the bucket on a collision.
.I HASH_POLICY_LINEAR_PROBE
resolves collisions with linear probes (e.g. by searching for the next
empty hash bucket).
.I HASH_POLICY_DOUBLE_HASH
is like linear probe, but searches in increments given by a
secondary hash function.
Note that the performance of the non-chain methods degrade severely as
the number of elements in the hash table approach the hash table size.
.PP
.I M
defines the minimum hash table size.  For some hash function types, M may be
increased to some prime number or power of 2 larger than the passed
value.
.PP
.I htype
defines the hashing function.  There are a few internally defined
hashing functions that may be specified.
.TP 10
\fBHASH_TYPE_OWN
says that you will be supplying a
.I hash
function and possibly a
.I shash
function.  M will be taken as given.  See the discussion of
.I hash
and
.I shash
below for more information.
.TP 10
\fBHASH_TYPE_DIVISION
is the simplest method.  The bucket is choosen on the basis of "key
modulo M".
.I hash_new
resizes the supplied M upwards until it is relatively prime to
2,3,5,7,11,13,17,19.  It would be best if M was prime such that M does
not divide (size of character set)^b plus/minus a where b and a are
small numbers; however, choosing M to be relatively prime to the prime
factors less than 20 should still give decent results.
The secondary hash for
HASH_TYPE_DIVISION
assumes
that M is prime and uses the function 1 + (K modulo (M - 2)).  Things
will work best if M and M-2 are twin primes like 1021 and 1019.  In
general, this will not be true and you should evaluate the
effectiveness on your data.
(See Knuth, Volume 3 for a full discussion).
.TP 10
\fBHASH_TYPE_MULTIPLICATIVE
forces up the passed M so that it is a power of 2 (call it 2^r).
The hash function used is AK>>(number of bits in a word - r) where A
is an integer constant relatively prime to 2^32 (for a 32 bit
machine).
A has been chosen to attempt fibonacci
hashing (whether this holds or not is debatable--futher research
required).  See A_MULTIPLIER in hash.h.
The secondary hash function takes r higher bits in the product defined
above and oring in a one (e.g. right shifts number of bits - 2*r).
(See Knuth, Volume 3, for a full discussion).
.PP
The
.I compare
function is a required user specified routine to compare a key (key) to a
stored data item (data).
It should return negative, zero, or positive if the comparision is
less than, equal to, or greater than respectively.
.PP
The
.I allocate
function allows one to insert data through
.I h_operation
without allocating it before hand.
If
.I allocate
is not given, it assume that the key is the data.
.PP
.I hash,
if non-null, defines the primary hash function that is used to compute
the bucket corresponding to the key.
.I shash,
if non-null, defines the secondary hash function used to obtain a
"movement" value for collision resolution for the open hash policies.
It is worth noting
that 
.I shash,
if specified, will be used by linear probing.
Specifying linear
probing versus double probing matters when no secondary hash function
is given.
The arguments to
.I hash
are the size of the hash
table, the log base 2 of the size of the hash table (not the floor
log2(M), but 2^r s.t. 2^r >= M), and the key K.
.I shash
also takes as a parameter (hidx) the value return by
.I hash.
Specification of the hash functions will override any specified by the
hash type argument; however, the passed value of M will still be
resized according to the passed hash type (e.g. for multiplicative,
M will be bumped until it is a power of 2).
.PP
.I compress
is used to compress a coerce a key to an unsigned
integer for the hash functions and to dereference the data pointed to by
key (which usually is a pointer).
It is generally required
for internal hashing
functions are used and optional otherwise (though your hash function
would have to do the compression if you don't supply this routine).
An example of a compress function for an
string would be:
.nf
	compress(s) unsigned char *s; 
	{
	  unsigned int j = 0;
	  while (*s) j += *s++;
	}
.fi
In this case, it is important to note that a simpler function like an
xor across the
data will make the range too small (unless the table is very small)
because you would only be making use of 8 bits for a maximum hash
range of 256.
(Note: this
compression function is only so-so, it would be better if it rotated
the data on every turn to ensure that all the bits come fully into
play--however, this is highly dependent upon the data the hashing
type).  Note, if you don't supply a compression function (e.g. specify
as NULL), then the key will be used directly.
This will cause
problems if sizeof(caddr_t) != sizeof(unsigned int), so consider this
carefully (i.e. don't do it -- pass a pointer to a variable containing
the key and write a dummy compress function that just returns the value).
.PP
.I chainops
will be describe in a later section in full detail.  Essentially, it
allows one to chain off the buckets in an arbritrary fashion (perhaps
with another hash table).  By default, it is done with an ordered linked
list.  Of course, it is only meanful when the policy selected is chain.
.PP
.I h_redefine
takes the hash table handle as an argument in addition to all the
other arguments of 
.I h_new.  
.I h_redefine
will reformat the hash table
according to the passed arguments.  It will rehash if the hash table
is valid (so it should not be called lightly).
.I WARNING:
If you want to use h_redefine, it is important that the "key" as
passed to the 
.I h_operation
routines is the same as the data stored in the buckets!  
This is necessary because
.I h_redefine
operates by calling
.I h_insert
with the items in the buckets as the key.
.PP
.I policy
and 
.I type
can be
specified as 
.I HASH_POLICY_OLD
and
.I HASH_TYPE_OLD respectively to retain
the old policy and type.  For 
.I compare, 
.I allocate, 
.I hash, 
.I shash,
.I compress, and 
.I chainops,
pass NULL unless you wish to change those functions.  Set M
to be zero to retain the old table size (note, if a new hash type is
specified, the passed M may be resized).  It is expected that the main use
.I h_redefine
will be to increase the hash table size: use the macro 
.I h_rehash
for this.
.PP
.I h_free
will free a hash table.  It calls
.I free_func
on every item inserted into the table so that data can be released if
necessary.  If free_func is NULL, then it is assumed that the data
need not be released.
.SH Hash Operations
.I h_operation
provides insert, member, and delete operations on a hash table created
by h_new.  A high degree of control over its operation is provided.
The macros
.I h_insert,
.I h_delete,
and
.I h_member
hide the less commonly used arguments.
.PP
.I operation 
defines the operation to be performed.  It best if
.I key
is a pointer to data instead of the actual key.
.TP 10
\fB HASH_OP_MEMBER
finds an item based upon
.I key
and returns it.  If the item is not
in the table, NULL is returned.
The comparision function defined in 
.I h_new
is used to determine if the
item is in the table.
.TP 10
\fBHASH_OP_INSERT
is like find, but the item is inserted if it wasn't already in the
table.
.I allocate,
if non-null,
as defined in
.I h_new
is called to get the data to be stored.  If
.I allocate
is NULL, then it assumed that the key is the data.
NULL is returned if the item could not be inserted because all the
buckets were filled or a memory allocation failed.
.TP 10
\fB HASH_OP_DELETE
will remove the specified item from the table and return it
if it was in the table.
NULL will be returned if the item was not in the
table.
.PP
.I hth
is the hash table handle as returned by
.I h_new.
.PP
.I key
is the used to match the data in the table.
Normally it is a pointer to some data item.
.PP
.I bkt,
and
.I dadvance
allow you to specify the hash bucket to use and the 
hash advance (default is 1) to use in open hashing collision
resolution respectively.
If
these are specified as negative numbers, the hash functions
defined in
.I h_new
will be used.
.PP
.I bucket
should be a pointer to an integer into which the primary bucket will
be returned (e.g. the index returned by primary hash function).
.I distance
is set to the number of buckets examined (beyond the first one) before
the item as added.
.SH Chaining off buckets
The default action for chaining off a bucket is to use a linked list
ordered largest to smallest (as defined by the comparision function).
It is possible to define an arbitrary method by defining a set of
chain operations.  The functions needed are defined below and should be put 
in a struct hash_bucket_list_ops and passed upon a hash table create.
.nf
	struct hash_bucket_list_ops {
	  caddr_t (*hlo_find)();
	  caddr_t (*hlo_insert)();
	  int (*hlo_delete)();
	  caddr_t (*hlo_get)();	/* get any and remove */
	};
.fi
.PP
In the following discussion, 
.I bp
is where information about the "list"
is stored.  "list" is used to mean your storage mechanism.  It could
be linked list, hash table, array, etc.
.I bp
allows you to disambiguate which list--unless your hash table size is
one, you must support more than one list.  An item in the following is
an abstract entity that can be compared against a key by the
.I compare
function provided in
.I h_new.
.I hlo_find,
.I hlo_insert,
and
.I hlo_delete
are matched functions.
.I hlo_find
is always called before
.I hlo_insert
or
.I hlo_delete
and the hash table functions will only call insert or delete if the
item (defined by the key) is not in the list
and in the list respectively.
.PP
.nf
caddr_t (*hlo_find)(bp, key, cmp, distance, hint, hint2)
caddr_t bp;
caddr_t key;
int (*cmp)();
int *distance;
caddr_t *hint;
caddr_t *hint2;
.fi
.I hlo_find
is used to see if the specified item is in the list based upon the
key.  It should return
the the item stored in the list if there and NULL
otherwise.  If non-null, this is the value that will be returned by
.I h_operation.
If the return value will be non-null, then
.I distance
should be set to
some metric by this function (e.g. distance from head of list on
linked list).  
.I cmp
is a comparision function to use (as passed in h_new).
.I hint,
and
.I hint2
are places to store hints for
.I hlo_insert
and
.I hlo_delete.
.PP
.nf
caddr_t (*hlo_insert)(bp, key, allocate, distance, hint, hint2)
caddr_t *bp;
caddr_t key;
caddr_t (*allocate)();
int *distance;
caddr_t hint1;
caddr_t hint2;
.fi
.I hlo_insert
should insert an item onto the list.  It should call
.I allocate,
if defined, to create the item based upon the key.  The distance should
be updated with respect to your metric set.
.I hint,
and
.I hint2
are passed as set by the
.I hlo_find.
You should set the bucket pointer to point to your "list" if the list
was empty before (e.g. *bp = head_of_list, *bp = hash_table_handle,
etc.).
.I hlo_insert
should return the stored data.  If it cannot insert the
item it may return NULL
.I hlo_insert's
value will be the value
returned by 
.I hlo_operation.
.PP
.nf
int (*hlo_delete)(bp, key, distance, hint, hint2)
caddr_t *bp;
caddr_t key;
int *distance;
caddr_t hint;
caddr_t hint2;
.fi
.I hlo_delete
should remove the specified item from the list.  It should return TRUE
on success and FALSE on failure.  distance should be set to the
distance of the deleted item with respect to the arbritry metric
defined for your set of functions.  The bucket pointer should be set
to NULL if there are no longer items in the list (e.g. *bp = NULL).
.I hint
and
.I hint2
are passed as set by the last 
.I hlo_find
operation.
.PP
.nf
caddr_t (*hlo_get)(bp)
caddr_t *bp;
.fi
.I hlo_get
is used by the
.I h_redefine
and
.I h_free
functions.
It should unlink an abritrary item from the list and return it.
.PP
The following simple set of functions define a hash table with no
collisions allowed:
.nf
	none_find(bp, key, cmp, distance, hint, hint2)
	caddr_t bp, key, *hint,*hint2;
	int (*cmp)(), *distance;
	{
	  *distance = 0;
	  if (bp == NULL)	/* nothing in list */
	    return(NULL);
	  if ((*cmp)(key,bp) == 0)
	    return(*bp);
	}

	caddr_t none_insert(bp, key, allocate, distance, hint, hint2)
	caddr_t *bp, key, *hint,*hint2;
	caddr_t (*dup)();
	{
	  *distance = 0;
	  *bp = allocate ? (*allocate)(key) : key;
	}

	int none_delete(bp, key, distance, hint, hint2)
	caddr_t *bp, key, *hint,*hint2;
	{
	  caddr_t v = *bp;
	  *distance = 0;
	  return(v != NULL);	/* true if we deleted */
	}

	caddr_t none_get(bp)
	caddr_t *bp;
	{
	  caddr_t r = *bp;
	  *bp = NULL;
	  return(r);
	}
.fi
.SH Statisitcs
.I h_statistics
returns a pointer to the following structure:
.nf
	struct hash_statistics {
	  int hs_buckets;	/* number of buckets in table */
	  /* describes # of entries in chain */	
	  int hs_used;		/* # of buckets filled */
	  /* describes table (not accurate for chain policy) */
	  int hs_davg;		/* average distance from hash */
	  int hs_dsum;		/* sum of distances from hash */
	  int hs_dmax;		/* maximum distance from hash */
	  /* describes lookup patterns (describes distance into */
	  /* linear table if the policy is chain */
	  int hs_lnum;		/* remember number of lookups */
	  int hs_lsum;		/* sum of lookup distances */
	  int hs_lavg;		/* average lookup distance */
	  /* cumulative for lookup patterns (describes overall */
	  /* efficiency) */
	  int hs_clnum;		/* remember number of lookups */
	  int hs_clsum;		/* sum of lookup distances */
	};
.fi
The averages are reported as a fixed point number with two decimal
digits of precision after the decimal point (e.g. avg/100.avg%100).
.PP
The lookup and table statistics are cleared on a
.I h_redefine
operation.
.SH NOTES
Some analysis of the hashing functions provided should be done to
determine how "good" they are.
.br
Allocate probably should have been called "get_item" in the above.
.br
Possibly some method for returning the "nth" or "next" item in the
hash table should be provided for times when it is necessary to access
the items in a linear fashion.  However, it possible to do this
already using the "allocate" call to put the items on a linked list or
in an array.
.SH RESTRICTIONS
Perhaps more control over the hashing functions should be provided;
however, it is easy enough to replace them.
.SH REFERENCES
Searching and Sorting, The Art of Computer Programming, Volume 3,
Donald E. Knuth.
.SH "SEE ALSO"
bsearch(3), lsearch(3), string(3), tsearch(3), hsearch(3)


SHAR_EOF
if test 17620 -ne "`wc -c < 'hash.3'`"
then
	echo shar: "error transmitting 'hash.3'" '(should have been 17620 characters)'
fi
chmod 440 'hash.3'
fi
if test -f 'hash.c'
then
	echo shar: "will not over-write existing file 'hash.c'"
else
cat << \SHAR_EOF > 'hash.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:36 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/hash.c,v 1.14 88/09/14 10:19:36 cck Rel $";
static char revision[] = "$Revision: 1.14 $";

/*
 * hash.h - external definitions for hash.c - generalized hashing function
 *
 *  written by Charlie C. Kim
 *     Academic Networking, Communications and Systems Group
 *     Center For Computing Activities
 *     Columbia University
 *   September 1988
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 5, 1988  CCKim Created
 *  Sept 6, 1988  CCKim Finished: level 0
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <sys/types.h>
#include "hash.h"

#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE 1
#endif
#ifndef PRIVATE
# define PRIVATE static
#endif


/*
 * holds and describes a hash table
 *
 * ht_policy: policy on collisions (cf hash.h)
 * ht_cfunc: takes (key, data) and returns true if they are equal
 * ht_afunc: takes a key and returns the item from higher up
 * ht_cpfunc: should compress the key to a integer -- only required if
 *    no hash function has been provided
 * ht_hfunc: takes (M, data) as arguments - returns hash index
 *    M is the sizeof(int)*8 - log of the table size if the hashing
 *     type is multiplicative
 * ht_hfunc2: is the secondary hashing function for double hashing
 * ht_chainops: chaining function other than linked list
 * ht_stats: describes performance of hash table
 * ht_type: hash function type
 * ht_buckets: pointer to the hash table buckets
 *
*/
typedef struct htable {
  int ht_M;			/* # of hash table buckes */
  int ht_logM;			/* M or log M (for certain hash types) */
  int ht_policy;		/* hashing policies for collision */
  /* alway call with passed key first, data item second */
  int (*ht_cfunc)();		/* comparision function */
  caddr_t (*ht_afunc)();	/* allocate function */
  u_int (*ht_cpfunc)();		/* compression function */
  u_int (*ht_hfunc)();		/* hashing function */
  u_int (*ht_hfunc2)();		/* secondary hashing function */
  struct hash_bucket_list_ops *ht_chainops; /* chain functions */
  int ht_type;			/* hash type */
  struct hash_statistics ht_stats; /* statisitics */
  caddr_t *ht_buckets;		/* actual hash table */
} HTABLE;

/* some hash functions */
PRIVATE u_int hash_multiplicative();
PRIVATE u_int hash_division();
PRIVATE u_int hash2_multiplicative();
PRIVATE u_int hash2_division();

/* list operations */
PRIVATE caddr_t list_find();
PRIVATE caddr_t list_insert();
PRIVATE int list_delete();
PRIVATE caddr_t list_get();

/* basic hash bucket chaining with an ordered link list */
PRIVATE struct hash_bucket_list_ops hash_chain_by_list = {
  list_find,
  list_insert,
  list_delete,
  list_get
};

/* table of primary hashfunctions */
PRIVATE u_int (*hash_funcs[HASH_TYPE_NUM])() = {
  NULL,
  hash_division,
  hash_multiplicative,
};

/* table of secondary hash functions */
PRIVATE u_int (*hash_funcs2[HASH_TYPE_NUM])() = {
  NULL,				/* own */
  hash2_division,
  hash2_multiplicative,
};

/* free a hash table - free_func gets called to free data */
void
h_free(ht, free_func)
HTABLE *ht;
int (*free_func)();
{
  caddr_t *bt;
  caddr_t p;
  int M, i, policy;
  caddr_t (*get_func)();
  
  M = ht->ht_M;			/* get # of entries */
  bt = ht->ht_buckets;		/* get buckets */
  ht->ht_buckets = NULL;	/* just in case... */
  if (ht->ht_chainops)
    get_func = ht->ht_chainops->hlo_get;
  policy = ht->ht_policy;
  if (bt == NULL)
    return;
  for (i = 0; i < M; i++) {
    if (bt[i] == NULL)
      continue;
    switch (policy) {
    case HASH_POLICY_CHAIN:
      if (get_func == NULL)
	break;
      while ((p = (*get_func)(&bt[i])))
	if (free_func)
	  (*free_func)(p);
      break;
    default:
	if (free_func)
	  (*free_func)(bt[i]);
    }
  }
  free((caddr_t)bt);			/* free old table */
  free((caddr_t)ht);
}

/* setup a new hash table, returns handle for hash table */
caddr_t
h_new(policy, hashtype, M, cfunc, afunc, cpfunc, hfunc, hfunc2, chainops)
int policy;
int hashtype;			/* hash type */
int M;
int (*cfunc)();			/* comparision function */
caddr_t (*afunc)();			/* allocate function */
u_int (*cpfunc)();		/* compression function */
u_int (*hfunc)();		/* hash function */
u_int (*hfunc2)();		/* secondary hash function */
struct hash_bucket_list_ops *chainops;
{
  HTABLE *htable;

  if (cfunc == NULL) {		/* required! */
    fprintf(stderr, "hash table create: no compare function\n");
    return(NULL);
  }
  if (!HASH_TYPE_VALID(hashtype)) {
    fprintf(stderr, "hash table create: invalid type %d\n", hashtype);
    return(NULL);
  }
  if (hashtype == HASH_TYPE_OWN && hfunc == NULL) {
  fprintf(stderr, "hash table create: must give hash function when own set\n");
  return(NULL);
  }
  if (!HASH_POLICY_VALID(policy)) {
    fprintf(stderr, "hash table create: invalid policy %d\n", policy);
    return(NULL);
  }
  if (M <= 0) {
    fprintf(stderr, "hash table create: invalid hash table size %d\n", M);
    return(NULL);
  }
  if ((htable = (HTABLE *)malloc(sizeof(HTABLE))) == NULL)
    return(NULL);
  htable->ht_policy = policy;
  htable->ht_cfunc = cfunc;
  htable->ht_afunc = afunc;
  htable->ht_hfunc = hash_funcs[hashtype];
  if (htable->ht_policy == HASH_POLICY_DOUBLE_HASH)
    htable->ht_hfunc2 = hash_funcs2[hashtype];
  else
    htable->ht_hfunc2 = NULL;
  /* override std. hash functions if specified */
  if (hfunc)
    htable->ht_hfunc = hfunc;
  if (hfunc2)
    htable->ht_hfunc2 = hfunc2;
  htable->ht_cpfunc = cpfunc;
  htable->ht_chainops = chainops ? chainops : &hash_chain_by_list;
  htable->ht_type = hashtype;
  bzero(&htable->ht_stats, sizeof(htable->ht_stats));
  htable->ht_stats.hs_buckets = M;
  htable->ht_M = 0;		/* assume these */
  return((caddr_t)h_redefine(htable,HASH_POLICY_OLD,HASH_TYPE_OLD, M,
			     NULL, NULL,NULL,NULL, NULL, NULL));
}

/* redefine an existing hash table, will rehash by creating new set of */
/* buckets and killing off old set */
caddr_t
h_redefine(ht, policy, hashtype, M, cfunc, afunc, cpfunc, hfunc, hfunc2,
	   chainops)
HTABLE *ht;
int policy;			/* hashing policy */
int hashtype;			/* hashing type */
int M;				/* size */
int (*cfunc)();			/* comparision function */
caddr_t (*afunc)();		/* allocate function */
u_int (*hfunc)();		/* hash function */
u_int (*hfunc2)();		/* secondary hash function */
u_int (*cpfunc)();		/* compression function */
struct hash_bucket_list_ops *chainops;
{
  int logM, oldM, i, oldPolicy;
  struct hash_bucket_list_ops *oldChainOps;
  caddr_t *bt, *nbt;
  caddr_t p;

  if (!HASH_TYPE_VALID(hashtype) && hashtype != HASH_TYPE_OLD) {
    fprintf(stderr, "hash table create: invalid type %d\n", hashtype);
    return(NULL);
  }
  if (!HASH_POLICY_VALID(policy) && policy != HASH_POLICY_OLD) {
    fprintf(stderr, "hash table create: invalid policy %d\n", policy);
    return(NULL);
  }
  if (M <= 0)			/* zero means base on old */
    M = ht->ht_M;
  if (hashtype == HASH_TYPE_OLD)
    hashtype = ht->ht_type;	/* get old */
  logM = 0;
  switch (hashtype) {
  case HASH_TYPE_MULTIPLICATIVE:
    i = M >> 1;
    M = 1;
    logM = 0;
    while (i) {			/* while M is still about */
      i >>= 1;			/* divide by 2 */
      M <<= 1;			/* multiply by 2 */
      logM++;
    }
    break;
  case HASH_TYPE_DIVISION:
    M += (1 - (M%2));		/* make odd */
    /* scale up M so it isn't relatively prime for these small primes */
    /* c.f. Fundamental of Data Structures, Horowitz and Sahni, pp. 461 */
    while (!((M%3) && (M%5) && (M%7) && (M%11) && (M%13) && (M%17)&&(M%19)))
      M+=2;
    break;
  default:
    break;
  }
  if (M <= ht->ht_M)		/* nothing to do */
    return((caddr_t)ht);
  if (logM == 0) {		/* no logM?  figure it */
    int t = M>>1;		/* get M */
    do {
      logM++;			/* int log M to 1 */
      t >>= 1;			/* divide by 2 */
    } while (t);
  }
  bt = ht->ht_buckets;		/* get buckets */
  oldM = ht->ht_M;
  oldPolicy = ht->ht_policy;
  oldChainOps = ht->ht_chainops;

  if ((nbt = (caddr_t *)calloc((u_int)M, sizeof(caddr_t))) == NULL) {
    fprintf(stderr, "hash table create: no memory for %d element table\n",M);
    return(NULL);		/* return */
  }
  ht->ht_buckets = nbt;	/* save new bucket table */
  ht->ht_M = M;		/* assume these */
  ht->ht_logM = logM;
  ht->ht_stats.hs_buckets = M; /* mark # of buckets */
  ht->ht_policy = (policy == HASH_POLICY_OLD) ? oldPolicy : policy;
  if (afunc)
    ht->ht_afunc = afunc;
  if (cfunc)
    ht->ht_cfunc = cfunc;
  if (ht->ht_type != hashtype && hashtype != HASH_TYPE_OLD) {
    ht->ht_hfunc = hash_funcs[hashtype];
    if (ht->ht_policy == HASH_POLICY_DOUBLE_HASH)
      ht->ht_hfunc2 = hash_funcs2[hashtype];
    else
      ht->ht_hfunc2 = NULL;
  }
  /* always reset if given */
  if (hfunc)
    ht->ht_hfunc = hfunc;
  if (hfunc2)
    ht->ht_hfunc2 = hfunc2;
  if (cpfunc)
    ht->ht_cpfunc = cpfunc;
  if (chainops)
    ht->ht_chainops = chainops;
  ht->ht_type = hashtype;
  {
    struct hash_statistics *s = &ht->ht_stats;
    /* no longer valid */
    s->hs_used = s->hs_davg = s->hs_dsum = s->hs_dmax = 0;
    /* no longer valid */
    s->hs_lnum = s->hs_lsum = s->hs_lavg = 0;
    /* cum. statistics stay */
  }
  /* rehash if new table */
  if (bt) {
    afunc = ht->ht_afunc;	/* save */
    ht->ht_afunc = NULL;		/* turn off for a bit */
    for (i = 0; i < oldM; i++) {
      if (bt[i]) {
	switch (oldPolicy) {
	case HASH_POLICY_CHAIN:
	  while ((p = (*oldChainOps->hlo_get)(&bt[i])))
	    h_insert(ht, p);
	  break;
	default:
	  h_insert(ht, bt[i]);
	}
      }
    }
    ht->ht_afunc = afunc;	/* turn back on */
    free((caddr_t)bt);		/* free old table */
  }
  return((caddr_t)ht);
}

/* update hash TABLE statistics: generally, these are off for chain */
/* and when there are deletes done */ 
PRIVATE int
update_hash_table_stats(s, distance, updown)
struct hash_statistics *s;
int distance;
int updown;
{
  if (distance > s->hs_dmax) /* new maximum distance */
    s->hs_dmax = distance;
  s->hs_dsum += distance;	/* bump sum of distances */
  s->hs_used += updown;
  if (s->hs_used)
    s->hs_davg = (100*s->hs_dsum) / s->hs_used; /* scale it */
  else
    s->hs_davg = 0;
  return(s->hs_davg);
}

/* update lookup statisitics */
PRIVATE int
update_hash_lookup_stats(s, distance)
struct hash_statistics *s;
int distance;
{
  s->hs_lsum += distance;	/* bump sum of distances */
  s->hs_lnum++;			/* bump number of distances */
  s->hs_clsum += distance;	/* same for cum. */
  s->hs_clnum++;
  s->hs_lavg = (100*s->hs_lsum) / s->hs_lnum; /* save 2 decimal points */
  return(s->hs_lavg);
}

/* hash table operation: delete, insert, find */
caddr_t
h_operation(what, ht, key, idx, idx2, d, b)
int what;
HTABLE *ht;
caddr_t key;
int idx;			/* preliminary index (-1 if none) */
int idx2;			/* secondary index (-1 if none) */
int *d;				/* return distance ? */
int *b;				/* return bucket # */
{
  int sidx, t;
  int distance;
  u_int cpkey;			/* compress version of key */
  caddr_t *bp;			/* bucket pointer */
  caddr_t *pbp = NULL;		/* previous bucket pointer for delete */
  caddr_t data = NULL;

  /* blather */
  if (ht == NULL || HASH_OP_INVALID(what))
    return(NULL);
  if (idx < 0) {
    if (ht->ht_cpfunc) {
      cpkey = (*ht->ht_cpfunc)(key);
      idx = (*ht->ht_hfunc)(ht->ht_M, ht->ht_logM, cpkey);
    } else
      idx = (*ht->ht_hfunc)(ht->ht_M, ht->ht_logM, key);
  }
  sidx = idx;
  if (ht->ht_buckets == NULL) {
    fprintf(stderr, "No buckets for hash table!  (Possibly using a freed \
 hash table handle)\n");
    return(NULL);
  }
  bp = &ht->ht_buckets[idx];	/* start */
  distance = 0;
  if (b)
    *b = sidx;

  if (ht->ht_policy == HASH_POLICY_CHAIN) {
    caddr_t hint, hint2;

    /* distance should be updated */
    data = (*ht->ht_chainops->hlo_find)(*bp,key,ht->ht_cfunc,
					&distance, &hint, &hint2);
    switch (what) {
    case HASH_OP_DELETE:
      if (!data)
	break;
      /* key */
      /* ignore error (should not happen!) */
      (void)(*ht->ht_chainops->hlo_delete)(bp, key, &distance, hint, hint2);
      update_hash_table_stats(&ht->ht_stats, -distance, -1);
      break;
    case HASH_OP_MEMBER:
      if (data)
	t = update_hash_lookup_stats(&ht->ht_stats, distance);
      break;
    case HASH_OP_INSERT:
      if (data) {
	t = update_hash_lookup_stats(&ht->ht_stats, distance);
	break;
      }
      data= (*ht->ht_chainops->hlo_insert)(bp,key,ht->ht_afunc,
					   &distance, hint,hint2);
      update_hash_table_stats(&ht->ht_stats, distance, 1);
      break;
    }
    if (d)
      *d = distance;
    return(data);
  }

  do {
    if (*bp == NULL) {
      switch (what) {
      case HASH_OP_DELETE:		/* finished delete */
	break;
      case HASH_OP_MEMBER:
	data = NULL;
	break;
      case HASH_OP_INSERT:
	/* left with insert */
	data = ht->ht_afunc ? (*ht->ht_afunc)(key) : key;
	*bp = data;
	update_hash_table_stats(&ht->ht_stats, distance, 1);
	break;
      }
      if (d)
	*d = distance;
      return(data);
    } else {
      switch (what) {
      case HASH_OP_DELETE:
	/* if we haven't found an key to delete, try to find it */
	if (!pbp) {
	  if ((*ht->ht_cfunc)(key, *bp) == 0) {
	    data = *bp;		/* save return key */
	    *bp = NULL;		/* clear out this bucket */
	    pbp = bp;		/* remember this bucket */
	    update_hash_table_stats(&ht->ht_stats, -distance, -1);
	  }
	} else {
	  /* delete old distance */
	  update_hash_table_stats(&ht->ht_stats, -distance, -1);
	  /* insert new distance */
	  update_hash_table_stats(&ht->ht_stats, distance-1, 1);
	  *pbp = *bp;		/* move bucket */
	  *bp = NULL;		/* clear out this bucket */
	  pbp = bp;		/* remember this bucket */
	}
      default:
	if ((*ht->ht_cfunc)(key, *bp) == 0) {
	  t = update_hash_lookup_stats(&ht->ht_stats, distance);
	  if (d)
	    *d = distance;
	  return(*bp);		/* done */
	}
      }
    }
    if (idx2 < 0 && ht->ht_hfunc2)
      if (ht->ht_cpfunc) 
	idx2 = (*ht->ht_hfunc2)(ht->ht_M, ht->ht_logM, idx, cpkey);
      else
	idx2 = (*ht->ht_hfunc2)(ht->ht_M, ht->ht_logM, idx, key);
    distance++;
    idx += idx2 > 0 ? idx2 : 1; /* bump index */
    bp++;			/* advance bucket pointer */
    if (idx >= ht->ht_M) {	/* need to wrap around */
      idx %= ht->ht_M;		/* push index about */
      bp = &ht->ht_buckets[idx]; /* and reset buckets */
    }
  } while (sidx != idx);
  return(NULL);
}

/* return hash statistics */
struct hash_statistics *
h_statistics(h)
HTABLE *h;
{
  return(&h->ht_stats);
}


/* for linked list */
struct hash_chain_item {
  struct hash_chain_item *hci_next; /* pointer to next item in chain */
  caddr_t hci_data;		/* pointer to data */
};

/*
 * hint == previous(hint2)
 *  hint2 is the match node or node whose data is > than current
 *
*/
PRIVATE caddr_t
list_find(h, key, cmp, distance, hint, hint2)
struct hash_chain_item *h;
caddr_t key;
int (*cmp)();
int *distance;
struct hash_chain_item **hint;
struct hash_chain_item **hint2;
{
  struct hash_chain_item *hp = NULL;
  int d,c;

  *distance = 0;		/* no distnace */
  *hint = NULL;			/* mark no hint */
  *hint2 = NULL;
  if (h == NULL)
    return(NULL);
  for (d = 0 ; h ; h = h->hci_next) {
    if ((c = (*cmp)(key, h->hci_data)) >= 0)
      break;
    d++;
    hp = h;
  }
  if (distance)
    *distance = d;
  if (hint2)
    *hint2 = h;
  if (hint)
    *hint = hp;
  return(c == 0 ? h->hci_data : NULL);
}

/*
 * insert item into chain.  hint is from the lookup and helps us insert
 * distance is from lookup too (we could choose to change)
 *
 * hint == previous(hint2)
 *  hint2 is the match node or node whose data is > than current
 * return 0 on success, -1 on failure.
 *
 */
/*ARGSUSED*/
PRIVATE caddr_t
list_insert(head, key, alloc, distance, hint, hint2)
caddr_t *head;
caddr_t key;
caddr_t (*alloc)();
int *distance;
struct hash_chain_item *hint;
struct hash_chain_item *hint2;
{
  struct hash_chain_item *h;

  h = (struct hash_chain_item *)malloc(sizeof(struct hash_chain_item));
  if (h == NULL)
    return(NULL);
  h->hci_data = alloc ? (*alloc)(key) : key;
  h->hci_next = hint2;
  if (hint)
    hint->hci_next = h;
  else
    *head = (caddr_t)h;
  return(h->hci_data);
}

/*
 * assumes a find has been done, hint is set by find and item exists
 *  in the list
 * head - head of list
 * item - data (unused)
 * hint - previous node to one that contains item
 * distance - distance to update (not done) (may be deleted)
 *
*/
/*ARGSUSED*/
PRIVATE int
list_delete(head, key, distance, hint, hint2)
caddr_t *head;
caddr_t key;
int *distance;			/* not used */
struct hash_chain_item *hint;
struct hash_chain_item *hint2;
{
  /* trust our input: two things could be wrong, first */
  /* hint2 == NULL ==> nothing to delete */
  /* hint2 != "key" ==> item not in list */
  if (hint == NULL) {
    *head = (caddr_t)hint2->hci_next;	/* remove */
    free((caddr_t)hint2);
    return(TRUE);
  }
  hint->hci_next = hint2->hci_next; /* unlink */
  free((caddr_t)hint2);		/* get rid of node */
  return(TRUE);
}

/* gets first item on list and returns data, freeing up node */
PRIVATE caddr_t
list_get(h)
struct hash_chain_item **h;
{
  struct hash_chain_item *n;
  caddr_t d;

  if (h == NULL || *h == NULL)
    return(NULL);
  n = *h;			/* get item */
  *h = n->hci_next;		/* and remove */
  d = n->hci_data;
  free((caddr_t)n);
  return(d);
}

/* do hash division method */
/*ARGSUSED*/
PRIVATE u_int
hash_division(M, logM, idx)
int M;
int logM;
u_int idx;
{
  return(idx % M);
}

/* will work will with M if M-2,M are twin primes */
/*ARGSUSED*/
PRIVATE u_int
hash2_division(M, logM, hidx, idx)
int M;
int logM;
u_int hidx;
u_int idx;
{
  return(1 + (idx % (M-2)));
}

/* handle multiplicative method - hopefully the multiplier gives us */
/* good range */
/*ARGSUSED*/
PRIVATE u_int
hash_multiplicative(M, logM, idx)
int M;
int logM;
u_int idx;
{
  return(((u_int)idx*A_MULTIPLIER>>(8*sizeof(int)-logM)));
}

/* the r more bits -- should be indepdent of the first r bits */
/*ARGSUSED*/
PRIVATE u_int
hash2_multiplicative(M, logM, hidx, idx)
int M;
int logM;
u_int hidx;
u_int idx;
{
  return(((u_int)idx*A_MULTIPLIER>>(8*sizeof(int)-logM-logM)|1) );
}

#ifdef TESTIT
/* test program */
u_int
docomp(data)
char *data;
{
  u_int j;
  j = 0;
  while (*data)
    j = ((j + *data++) >> 1) | j<<31;
  return(j);
}

char *
alloc_func(p)
char *p;
{
  char *d = (caddr_t)malloc(strlen(p) + 1);
  strcpy(d, p);
  return(d);
}

dumpstats(msg, s)
char *msg;
struct hash_statistics *s;
{
  printf("%s\n\t %d bkts used, avg dist = %d.%02d, max dist = %d\n",
	 msg,
	 s->hs_used, s->hs_davg/100, s->hs_davg % 100,
	 s->hs_dmax);
}

main()
{
  HTABLE *hpc, *hplp, *hpdh;
  extern strcmp();
  char buf[BUFSIZ];
  int b, d, op;
  char *p;

#define X 16 

  hpc = (HTABLE *)h_new(HASH_POLICY_CHAIN, HASH_TYPE_DIVISION, X,
			strcmp, alloc_func, docomp, NULL, NULL, NULL);
  hplp = (HTABLE *)h_new(HASH_POLICY_LINEAR_PROBE,
			 HASH_TYPE_MULTIPLICATIVE, X, strcmp,
			 alloc_func, docomp, NULL, NULL, NULL);
  hpdh = (HTABLE *)h_new(HASH_POLICY_DOUBLE_HASH,
			 HASH_TYPE_MULTIPLICATIVE, X, strcmp,
			 alloc_func, docomp, NULL, NULL, NULL);
  while (gets(buf) != NULL) {
    p = buf+1;
    switch (buf[0]) {
    case '+':
      printf("INSERT %s\n", buf+1);
      op = HASH_OP_INSERT;
      break;
    case '-':
      printf("DELETE %s\n", buf+1);
      op = HASH_OP_DELETE;
      break;
    case ' ':
      printf("FIND %s\n", buf+1);
      op = HASH_OP_MEMBER;
      break;
    default:
      op = HASH_OP_INSERT;
      p = buf;
    }
    if ((h_operation(op, hpc, p, -1, -1, &d, &b)))
      printf("chain: %s at distance %d from bucket %d\n", p, d,b);
    else
      printf("chain hash table overflow or item not in table\n");
    if ((h_operation(op, hplp, p, -1, -1, &d, &b)))
      printf("linear probe: %s at distance %d from bucket %d\n", p, d,b);
    else
      printf("linear probe hash table overflow or item not in table\n");
    if ((h_operation(op, hpdh, p, -1, -1, &d, &b)))
      printf("double hash: %s at distance %d from bucket %d\n", p, d,b);
    else
      printf("double hash table overflow or item not in table\n");
  }
  dumpstats("double hash with multiplicative hash", h_statistics(hpdh));
  h_redefine(hpdh, HASH_POLICY_CHAIN,HASH_TYPE_DIVISION, X, NULL,
	     NULL, NULL, NULL,NULL,NULL);
  dumpstats("redefine above as chain with division hash", h_statistics(hpdh));
  h_redefine(hpdh, HASH_POLICY_LINEAR_PROBE,HASH_TYPE_MULTIPLICATIVE,
	     X, NULL,NULL,NULL,NULL,NULL,NULL);
  dumpstats("redefine above as linear probe with multiplicative hash",
	    h_statistics(hpdh));
  dumpstats("chain with division hash", h_statistics(hpc));
  dumpstats("linear probe with multiplicative hash", h_statistics(hplp));
  h_free(hpdh, free);
}
#endif
SHAR_EOF
if test 21233 -ne "`wc -c < 'hash.c'`"
then
	echo shar: "error transmitting 'hash.c'" '(should have been 21233 characters)'
fi
chmod 440 'hash.c'
fi
if test -f 'hash.h'
then
	echo shar: "will not over-write existing file 'hash.h'"
else
cat << \SHAR_EOF > 'hash.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:40 $
 * $Header: /src/local/mac/cap/etalk/RCS/hash.h,v 1.11 88/09/14 10:19:40 cck Rel $
 * $Revision: 1.11 $
*/

/*
 * hash.h - external definitions for hash.c - generalized hashing function
 *
 *  written by Charlie C. Kim
 *     Academic Networking, Communications and Systems Group
 *     Center For Computing Activities
 *     Columbia University
 *   September 1988
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 5, 1988  CCKim Created
 *  Sept 6, 1988  CCKim Finished: level 0
 *
*/

#ifndef _HASH_HEADER_INCLUDED
#define _HASH_HEADER_INCLUDED "yes"

/* hash table operations recognized */
#define HASH_OP_DELETE 2
#define HASH_OP_MEMBER 1 
#define HASH_OP_INSERT 0
#define HASH_OP_NUM 3

#define HASH_OP_VALID(n) ((n) < HASH_OP_NUM && (n) >= HASH_OP_INSERT)
#define HASH_OP_INVALID(n) ((n) >= HASH_OP_NUM || (n) < HASH_OP_INSERT)

/* hashing collision policies */
#define HASH_POLICY_OLD -1	/* old for redefine */
#define HASH_POLICY_CHAIN 0	/* chain on collisions */
#define HASH_POLICY_LINEAR_PROBE 1 /* linear probes on collisions */
#define HASH_POLICY_DOUBLE_HASH 2 /* double hash on collisions */

/* given a hash policy, returns true if it is a valid policy */
#define HASH_POLICY_VALID(n) ((n) <= HASH_POLICY_DOUBLE_HASH && \
			      (n) >= HASH_POLICY_CHAIN)
#define HASH_POLICY_LINKSNEEDED(n) ((n) == HASH_POLICY_CHAIN)

/* hash function types */
#define HASH_TYPE_OLD -1	/* old for redefine */
#define HASH_TYPE_OWN 0		/* our own */
#define HASH_TYPE_DIVISION 1	/* division method */
#define HASH_TYPE_MULTIPLICATIVE 2 /* multiplicative method */
/* for multiplicative mode: try for fibonacci */
/* only valid for 32 bit machines! */
#define A_MULTIPLIER 2630561063 /* gotta figure out a good one */

#define HASH_TYPE_NUM 3

#define HASH_TYPE_VALID(n) ((n) < HASH_TYPE_NUM && \
  (n) >= HASH_TYPE_OWN)


/*
 * structure to allow operations other than a linear list off a hash
 * bucket in the chain policy
 *
*/
struct hash_bucket_list_ops {
  caddr_t (*hlo_find)();	/* find a member */
  caddr_t (*hlo_insert)();	/* insert a member (returns ptr to */
				/* data) */
  int (*hlo_delete)();		/* delete a member */
  caddr_t (*hlo_get)();		/* get any member and remove from list */
};

/* averages are fixed decimal with 2 digits after the decimal */
/* they are kept in case we want to do something when average */
/* distances get too large */
struct hash_statistics {
  int hs_buckets;		/* number of buckets in table */
  /* describes # of entries in chain */
  int hs_used;			/* # of buckets filled */
  /* describes table (not accurate for chain policy) */
  int hs_davg;			/* average distance from hash index */
  int hs_dsum;			/* sum of distances from hash index */
  int hs_dmax;			/* maximum distance from hash index */
  /* describes lookup patterns (describes distance into linear table */
  /* if the policy is chain */
  int hs_lnum;			/* remember number of lookups */
  int hs_lsum;			/* sum of lookup distances */
  int hs_lavg;			/* average lookup distance */
  /* cumulative for lookup patterns (describes overall efficiency) */
  int hs_clnum;			/* remember number of lookups */
  int hs_clsum;			/* sum of lookup distances */
};

/* function declarations */
caddr_t h_new();		/* create new table */
caddr_t h_operation();		/* hash operations */
struct hash_statistics *h_statistics(); /* returns stats on a table */
void h_free();			/* free a table */
/* must specify policy and type */
caddr_t h_redefine();		/* redefine operating parameters for a */
				/* hash table, forces a rehash */
#define h_rehash(ht,M) (h_redefine((ht),HASH_POLICY_OLD,HASH_TYPE_OLD,\
				   (M),NULL,NULL,NULL,NULL,NULL,NULL))
/* call hash operation for these */
#define h_member(ht,key) (h_operation(HASH_OP_MEMBER,(ht),(key),-1,-1,\
				      NULL,NULL))
#define h_insert(ht,key) (h_operation(HASH_OP_INSERT,(ht),(key),\
					  -1,-1, NULL, NULL))
#define h_delete(ht,key) (h_operation(HASH_OP_DELETE,(ht),(key),-1,-1,\
				       NULL,NULL))


#endif /* _HASH_HEADER_INCLUDED */
SHAR_EOF
if test 4540 -ne "`wc -c < 'hash.h'`"
then
	echo shar: "error transmitting 'hash.h'" '(should have been 4540 characters)'
fi
chmod 440 'hash.h'
fi
if test -f 'if_desc.h'
then
	echo shar: "will not over-write existing file 'if_desc.h'"
else
cat << \SHAR_EOF > 'if_desc.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:42 $
 * $Header: /src/local/mac/cap/etalk/RCS/if_desc.h,v 1.3 88/09/14 10:19:42 cck Rel $
 * $Revision: 1.3 $
*/

/*
 * if_desc.h - interface description
 *
 *  describes parameters for a interface
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 8, 1988  CCKim Created
 *
*/

#ifndef _IF_DESC_INCLUDED
#define _IF_DESC_INCLUDED "yes"
# include "mpxddp.h"
/*
 * description of a supported LAP type 
 *
*/
typedef struct lap_description {
  char *ld_name;		/* name of lap  */
  char **ld_key;		/* array of keywords to use for lap type */
  int ld_wants_data;		/* needs more than key */
  int (*ld_init_routine)();	/* (id, async) */
  int (*ld_stats_routine)();	/* (fd, id) */
  int (*ld_dump_routine)();	/* (fd, id) */
} LDESC_TYPE;

/*
 * an interface description (port description)
 *
 * Call ld_init_routine with this (+ async flag if you want to run
 *   async if possible) 
 * All fields except id_ifuse are not touched by ld_init_routine
*/
typedef struct interface_description {
  struct lap_description *id_ld; /* lap description */
  char *id_intf;		/* interface name */
  int id_intfno;		/* interface # */
  struct mpxddp_module *id_local; /* local delivery */
  int id_isabridge;		/* flag */
  int id_network;		/* network number (net order) */
  byte *id_zone;		/* zone name (pstring) */
  struct interface_description *id_next; /* next in list */
  caddr_t id_ifuse;		/* interface use */
} IDESC_TYPE;
#endif
SHAR_EOF
if test 1956 -ne "`wc -c < 'if_desc.h'`"
then
	echo shar: "error transmitting 'if_desc.h'" '(should have been 1956 characters)'
fi
chmod 440 'if_desc.h'
fi
if test -f 'kip_mpx.c'
then
	echo shar: "will not over-write existing file 'kip_mpx.c'"
else
cat << \SHAR_EOF > 'kip_mpx.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:45 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/kip_mpx.c,v 1.19 88/09/14 10:19:45 cck Rel $";
static char revision[] = "$Revision: 1.19 $";

/*
 * kip_mpx.c - talks to cap processes via udp
 *
 *  demultiplexing communications point with various processes.
 *  Two versions in here: one version speaks directly to KIP module
 *  CAP clients (KIP).  The other speaks on a modified range (MKIP)
 *  
 *  KIP writes atalk.local and cannot be used when the rebroadcaster
 *    is in use.  NOTE: THIS DOES NOT WORK CORRECTLY.
 *  MKIP writes matalk.local (debugging mostly)
 *
 *  This version really cheats and uses a different range of ports,
 *  but the same encapsulation used for kip.
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Edit History:
 *
 *  April 3, 1988  CCKim Created
 *
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>
#include "mpxddp.h"
#include "gw.h"
#include "log.h"

#define LOG_STD 2

/*
  ethertalk to kip - try 1:
  o forward packets by sending to ddp udp port
  o receive packets by having the remote send to "bridge" socket
  o much like kip methods except different socket range and fewer
    conditions in lower level code
*/

#ifndef MTAB
# define MTAB "/etc/matalk.local"
#endif
#ifndef TAB
# define TAB "/etc/atalk.local"
#endif

/* handle kip fakeout */
#define mkip_rebPort 903
#define kip_rebPort 902

#define mddp2ipskt(s)  (((s)&0x80) ? (s)+ddpNWKSUnix :(s)+200)

struct kipdef {
 int s;				/* socket */
 word net;
 byte node;
 byte bridge_node;
 struct sockaddr_in sin;
};

#define MKIP_HANDLE 0
#define KIP_HANDLE 1
#define NUM_KIP_HANDLE 2

#define VALID_HANDLE(h) ((h) >= 0 && (h) < NUM_KIP_HANDLE)
#define INVALID_HANDLE(h) ((h) >= NUM_KIP_HANDLE || (h) < 0)
private struct kipdef kipdefs[NUM_KIP_HANDLE];

#define KIP_PRIMARY 0		/* primary listening */
#define KIP_SECONDARY 1		/* secondary: local to process */

/* destination */
private struct in_addr desthost;

private DDP kddp;
private LAP klap;
private char kbuf[ddpMaxData+ddpSize]; /* add in space for lap */

private int kip_init();
private int mkip_init();
private int kip_grab();
private int kips_ahoy();
private int kip_sendddp();
private int kip_havenode();
private int kip_havenet();
private int kip_havezone();

export struct mpxddp_module mkip_mpx = {
  "modified KIP forwarding",
  "MKIP",
  mkip_init,
  NULL,				/* grab, don't do for now (ddpsrvc not done) */
  kip_sendddp,
  kip_havenode,
  kip_havenet,
  kip_havezone
};

export struct mpxddp_module kip_mpx = {
  "KIP forwarding",
  "KIP",
#ifdef notdef
  /* doesn't work right because of the way kip routing is done, sigh */
  kip_init,
  NULL,				/* grab, don't do for now (ddpsrvc not done) */
  kip_sendddp,
  kip_havenode,
  kip_havenet,
  kip_havezone
#else
  NULL,				/* init */
  NULL,				/* grab, don't do for now (ddpsrvc not done) */
  NULL,				/* senddp */
  NULL,				/* havenode */
  NULL,				/* have net */
  NULL				/* have zone */
#endif
};

private
kip_listener(fd, k, level)
int fd;
struct kipdef *k;
int level;
{
  struct msghdr msg;
  struct iovec iov[3];
  int len;
  struct sockaddr_in from_sin;
  
  /* should check k */
  iov[0].iov_base = (caddr_t)&klap;
  iov[0].iov_len = lapSize;
  iov[1].iov_base = (caddr_t)&kddp;
  iov[1].iov_len = ddpSize;
  iov[2].iov_base = kbuf;
  iov[2].iov_len = ddpMaxData;
  msg.msg_name = (caddr_t) &from_sin;
  msg.msg_namelen = sizeof(from_sin);
  msg.msg_iov = iov;
  msg.msg_iovlen = 3;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((len = recvmsg(fd,&msg,0)) < 0) {
    log(LOG_LOG|L_UERR, "recvmsg: kip listener");
    return(len);
  }
  /* could do rebroadcaster work here */
  if (len < (lapSize+ddpSize))
    return(-1);
  len -= (lapSize + ddpSize);
  if (klap.type != lapDDP)
    return(-1);
  switch (level) {
  case KIP_PRIMARY:
    if (klap.src != k->node)	/* drop!  must have wrong configuration */
      return(-1);
    if (kddp.srcNode != k->node || (kddp.srcNet != k->net && kddp.srcNet != 0))
      return(-1);
    break;
  case KIP_SECONDARY:
    /* always accept? */
    break;
  }
  ddp_output(NULL, &kddp, (byte *)kbuf, len);
  return(0);
}

/*
 * initialize kip forwarding
 *
*/
private int
mkip_init()
{
  static int inited = FALSE;
  int i;

  if (inited)
    return(-1);
  if ((i = kips_ahoy(MKIP_HANDLE)) >= 0)
    inited = TRUE;
  return(i);
}

private int
kip_init()
{
  static int inited = FALSE;
  int i;

  if (inited)
    return(-1);
  if ((i = kips_ahoy(KIP_HANDLE)) >= 0)
    inited = TRUE;
  return(i);
}

private int
kips_ahoy(h)
{
  int cc;
  struct kipdef *k;

  if (h != MKIP_HANDLE && h != KIP_HANDLE)
    return(-1);
  k = &kipdefs[h];
  k->net = 0;
  k->node = 0;
  k->bridge_node = 0;
  desthost.s_addr = inet_addr("127.0.0.1");
  if (desthost.s_addr == -1)
    return(-1);
  /* no need to bind since we don't recv on this socket, just send... */
  if ((k->s = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
    log(LOG_LOG|L_UERR, "socket: kip init");
    return(-1);
  }
  k->sin.sin_family = AF_INET;
  switch (h) {
  case MKIP_HANDLE:
    k->sin.sin_addr.s_addr = desthost.s_addr;
    k->sin.sin_port = htons(mkip_rebPort);
    break;
  case KIP_HANDLE:
    k->sin.sin_addr.s_addr = INADDR_ANY;
    k->sin.sin_port = htons(kip_rebPort);
    break;
  }
  if ((cc = bind(k->s, (caddr_t)&k->sin, sizeof(k->sin))) < 0) {
    close(k->s);
    log(LOG_LOG|L_UERR, "bind: kip init");
    return(cc);
  }
  fdlistener(k->s, kip_listener, k, KIP_PRIMARY);
  return(h);
}

private int
kip_grab(hdl, skt)
int hdl;
int skt;
{
  int fd, e;
  struct sockaddr_in lsin;

  if (INVALID_HANDLE(hdl))
    return(-1);
  
  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    log(LOG_STD|L_UERR, "kip grab: socket for skt %d", skt);
    return(fd);
  }
  lsin.sin_family = AF_INET;
  /* bind only for localhost if mkip */
  switch (hdl) {
  case MKIP_HANDLE:
    lsin.sin_addr.s_addr = desthost.s_addr;
    lsin.sin_port = htons(ddp2ipskt(skt));
    break;
  case KIP_HANDLE:
    lsin.sin_addr.s_addr = INADDR_ANY;
    lsin.sin_port = (word)htons(mddp2ipskt(skt));
    break;
  }
  if (lsin.sin_port == 0) {	/* same swapped or unswapped */
    close(fd);			/* bad ddp socket */
    return(-1);
  }
  if ((e = bind(fd, (caddr_t)&lsin, sizeof(lsin))) < 0) {
    log(LOG_STD|L_UERR, "kip grab: bind for skt %d", skt);
    return(fd);
  }
  fdlistener(kipdefs[hdl].s, kip_listener, KIP_SECONDARY);
  return(fd);
}

/*
 * send along a ddp packet to the appropriate process
 *
*/
private int
kip_sendddp(hdl, ddp, data, cc)
int hdl;
DDP *ddp;
caddr_t data;
int cc;
{
  struct msghdr msg;
  word destskt;
  int err;
  struct iovec iov[4];
  LAP lap;
  int t;
  struct kipdef *k;

  if (hdl != MKIP_HANDLE && hdl != KIP_HANDLE)
    return(-1);
  k = &kipdefs[hdl];

  /* I THINK this is the right place for this -- this way, the demuxer */
  /* could possible handle multiple networks */
  if (ddp->dstNet != k->net && ddp->dstNode != k->node) /* drop */
    return(0);

  if (hdl == KIP_HANDLE)
    destskt = (word)htons(ddp2ipskt(ddp->dstSkt)+128);
  else
    destskt = (word)htons(mddp2ipskt(ddp->dstSkt));
  /* establish a dummy lap header */
  lap.type = lapDDP;
  lap.dst = k->node;
  lap.src = ddp->srcNode;
  iov[IOV_LAP_LVL].iov_base = (caddr_t) &lap; /* LAP header */
  iov[IOV_LAP_LVL].iov_len = lapSize; 	  /* size  */
  iov[IOV_DDP_LVL].iov_base = (caddr_t) ddp;
  iov[IOV_DDP_LVL].iov_len = ddpSize;
  /* figure out what the data length should be */
  t = (ntohs(ddp->length)&ddpLengthMask) - ddpSize;
  /* if data size passed down is too large, then truncate.  (sunos */
  /* trailing bytes or packet less than 60 bytes */
  /* so, pass back min of cc, length */
  iov[2].iov_len = (cc > t) ? t : cc;
  iov[2].iov_base = data;
  /* send through */
  k->sin.sin_addr = desthost;
  k->sin.sin_port = destskt;
  msg.msg_name = (caddr_t) &k->sin;
  msg.msg_namelen = sizeof(k->sin);
  msg.msg_iov = iov;
  msg.msg_iovlen = 3;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((err = sendmsg(k->s,&msg,0)) < 0)
    log(LOG_LOG|L_UERR, "sendmsg: kip write");
  return(err);  
}

/*
 * have node now  -- just remember
 *
*/
private int
kip_havenode(hdl, node)
int hdl;
byte node;
{
  if (hdl != MKIP_HANDLE && hdl != KIP_HANDLE)
    return(FALSE);
  kipdefs[hdl].node = node;
  return(TRUE);
}

/*
 * have network now
 *
*/
private int
kip_havenet(hdl, net, node)
word net;
byte node;
{
  if (hdl != MKIP_HANDLE && hdl != KIP_HANDLE)
    return(FALSE);
  kipdefs[hdl].net = net;
  kipdefs[hdl].bridge_node = node;
  return(TRUE);
}

/*
 * have zone: ready to go
 *
*/
private int
kip_havezone(hdl, zone)
int hdl;
byte *zone;
{
  FILE *fp;
  int i;
  struct kipdef *k;
  char *file;

  if (hdl != MKIP_HANDLE && hdl != KIP_HANDLE)
    return(FALSE);
  k = &kipdefs[hdl];
  file = (hdl == MKIP_HANDLE) ? MTAB : TAB;
  if ((fp = fopen(file,"w")) == NULL) {
    log(LOG_LOG|L_UERR, "can't open %s", file);
    return(FALSE);
  }
  fprintf(fp, "%d.%d %d ",
	  nkipnetnumber(k->net), nkipsubnetnumber(k->net),
	  k->node);
  /* translate it for atalkdbm - ugh */
  for (i = *zone++; i ; i--, zone++) {
    switch (*zone) {
    case ' ':
      putc(' ', fp);
      break;
    case '_':
      putc('_', fp);
      putc('_', fp);
      break;
    default:
      putc(*zone, fp);
    }
  }
  putc('\n', fp);
  fprintf(fp, "%d.%d %d 127.0.0.1\n",
	  nkipnetnumber(k->net), nkipsubnetnumber(k->net),
	  k->bridge_node);
  fclose(fp);
  return(TRUE);
}
SHAR_EOF
if test 10135 -ne "`wc -c < 'kip_mpx.c'`"
then
	echo shar: "error transmitting 'kip_mpx.c'" '(should have been 10135 characters)'
fi
chmod 440 'kip_mpx.c'
fi
if test -f 'log.c'
then
	echo shar: "will not over-write existing file 'log.c'"
else
cat << \SHAR_EOF > 'log.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:19:52 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/log.c,v 1.7 88/09/14 10:19:52 cck Rel $";
static char revision[] = "$Revision: 1.7 $";

/*
 * log.c - simple logging facility
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *   in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * 
 * Edit History:
 *
 *   Aug, 1988 CCKim created
 *
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <varargs.h>
#include "log.h"


/* current debug level */
static int dlevel;

#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif

/*
 * set debug level
 *
*/
get_debug_level()
{
  return(dlevel);
}
set_debug_level(n)
int n;
{
  dlevel = n;
}

/*
 * print message - use vprintf whenever possible (solves the problem
 * of using the varargs macros -- you must interpret the format).
 * This is something all machine should, but don't have :-)
 */

static FILE *lfp = stderr;


#ifdef NOVPRINTF
/* Bletch - gotta do it because pyramids don't work the other way */
/* (using _doprnt and &args) and don't have vprintf */
/* of course, there will be something that is just one arg larger :-) */
/*VARARGS1*/
log(level, fmt, a1,a2,a3,a4,a5,a6,a7,a8,a9,aa,ab,ac,ad,ae,af)
int level;
char *fmt;
#else
/*VARARGS*/
log(va_alist)
va_dcl
#endif
{
  long time();
  char *mytod();
#ifndef NOVPRINTF
  register char *fmt;
  va_list args;
  int level;
#endif
  int saveerr;
  extern int errno;
  extern int sys_nerr;
  extern char *sys_errlist[];

  if (lfp == NULL)		/* no logging? */
    return;

  saveerr = errno;
#ifndef NOVPRINTF
  va_start(args);
  level = va_arg(args, int);
  fmt = va_arg(args, char *);
#endif

  if (dlevel < (level & L_LVL))
    return;
  fprintf(lfp,"uab: %s ",mytod());

#ifndef NOVPRINTF
  vfprintf(lfp, fmt, args);
  va_end(args);
#else
  fprintf(lfp, fmt, a1,a2,a3,a4,a5,a6,a7,a8,a9,aa,ab,ac,ad,ae,af);
#endif
  if (level & L_UERR) {
    if (saveerr < sys_nerr)
      fprintf(lfp, ": %s", sys_errlist[saveerr]);
    else
      fprintf(lfp, ": error %d\n", saveerr);
  }
  putc('\n', lfp);
  fflush(lfp);
  if (level & L_EXIT)
    exit(1);
}

islogfile()
{
  return(lfp != NULL);
}

logfileis(filename, mode)
char *filename;
char *mode;
{
  FILE *fp;

  if ((fp = fopen(filename, mode)) != NULL) {
    log(0, "log file name %s", filename);
  } else {
    log(0|L_UERR, "couldn't open logfile %s", filename);
  }
  lfp = fp;			/* reset */
}

nologfile()
{
  if (lfp && lfp != stderr)
    fclose(lfp);
  lfp = NULL;
}

/*
 * return pointer to formatted tod in static buffer
 *
*/
static char *
mytod()
{
  long tloc;
  struct tm *tm, *localtime();
  static char buf[100];		/* should be large enough */

  (void)time(&tloc);
  tm = localtime(&tloc);
  if (tm->tm_year > 99)
    sprintf(buf, "%02d:%02d:%02d %02d/%02d/%04d ",
	    tm->tm_hour, tm->tm_min, tm->tm_sec,
	    tm->tm_mon+1, tm->tm_mday, tm->tm_year+1900);
  else
    sprintf(buf, "%02d:%02d:%02d %02d/%02d/%02d ",
	    tm->tm_hour, tm->tm_min, tm->tm_sec,
	    tm->tm_mon+1, tm->tm_mday, tm->tm_year);
  return(buf);
}
SHAR_EOF
if test 3519 -ne "`wc -c < 'log.c'`"
then
	echo shar: "error transmitting 'log.c'" '(should have been 3519 characters)'
fi
chmod 440 'log.c'
fi
if test -f 'log.h'
then
	echo shar: "will not over-write existing file 'log.h'"
else
cat << \SHAR_EOF > 'log.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:19:55 $
 * $Header: /src/local/mac/cap/etalk/RCS/log.h,v 1.3 88/09/14 10:19:55 cck Rel $
 * $Revision: 1.3 $
*/

/*
 * log.h - simple logging facility header file
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *   in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * 
 * Edit History:
 *
 *   Aug, 1988 CCKim created
 *
*/

/* logging flags */
#define L_UERR 0x20		/* want unix error message */
#define L_EXIT 0x10		/* exit after logging */
#define L_LVL 0xf		/* debug levels */
#define L_LVLMAX 15		/* maximum level */

SHAR_EOF
if test 974 -ne "`wc -c < 'log.h'`"
then
	echo shar: "error transmitting 'log.h'" '(should have been 974 characters)'
fi
chmod 440 'log.h'
fi
if test -f 'mpxddp.h'
then
	echo shar: "will not over-write existing file 'mpxddp.h'"
else
cat << \SHAR_EOF > 'mpxddp.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:20:05 $
 * $Header: /src/local/mac/cap/etalk/RCS/mpxddp.h,v 1.9 88/09/14 10:20:05 cck Rel $
 * $Revision: 1.9 $
*/

/*
 * demultipexor/multiplexor interface
 *
 *  used to send packets to/from processes from a central process that
 * handles incoming ddp packets from an interface that can only be
 * attached by a single process 
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August 1988  CCKim Created
 *
*/

#ifndef _MPX_DDP_INCLUDED
#define _MPX_DDP_INCLUDED "yes"
/* 
 * demuliplexing module interface point
 *
*/

struct mpxddp_module {
  char *mpx_name;		/* name of module (usually transport type) */
  char *mpx_key;		/* key for specification purposes  */
  int (*mpx_init)();		/* init routine */
  int (*mpx_grab)();		/* used to grab ddp sockets */
  int (*mpx_send_ddp)();	/* send ddp routine */
  int (*mpx_havenode)();	/* mark node known */
  int (*mpx_havenet)();		/* network & bridge (if nec) known */
  int (*mpx_havezone)();	/* zone known */
};

/*
 * mpx_init
 *
 * initialization
 *
 * int (*mpx_init)() - returns handle (hdl) for later use
 *
*/

/*
 * mpx_grab
 *
 *  mpx_grab should "grab" the specified ddp socket (in its own way)
 *  and forward packets received upon that ddp socket to (s)ddp_router
 *
 * int (*mpx_grab)(hdl, skt);
*/

/*
 * mpx_send_ddp
 *
 * used by the mpx process to send ddp packets to clients
 *
 * (*mpx_send_ddp)(hdl, DDP *ddp, caddr_t data, int data_len)
 */

/*
 *  used by multiplexing process to send back info on the current ddp world
 *
 * Ordering will always be: havenode, havenet, havezone
 * 
 * (*mpx_havenode)(hdl,byte node)
 * (*mpx_havenet)(hdl,word net, byte bridgenode)
 * (*mpx_havezone)(hdl,pstr zone)
 *
*/

#endif /* INCLUDE THIS FILE */
SHAR_EOF
if test 2239 -ne "`wc -c < 'mpxddp.h'`"
then
	echo shar: "error transmitting 'mpxddp.h'" '(should have been 2239 characters)'
fi
chmod 440 'mpxddp.h'
fi
if test -f 'node.h'
then
	echo shar: "will not over-write existing file 'node.h'"
else
cat << \SHAR_EOF > 'node.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:20:08 $
 * $Header: /src/local/mac/cap/etalk/RCS/node.h,v 1.2 88/09/14 10:20:08 cck Rel $
 * $Revision: 1.2 $
*/

/*
 * node.h - defines a "node" (as per RTMP id)
 *
 * will eventually be the header file for a node manager
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  Sept 4, 1988  CCKim Created
 *
*/

#ifndef _NODE_INCLUDED
#define _NODE_INCLUDED "yes"
/*
 * NODE, struct node_addr
 * 
 * describes a node address which can be up to 256 bits in length
 * (theoretically).  This would be the "lap" level node id which need
 * not bear any relationship to the ddp node.
 *
*/

#define MAXNODEBYTE 8
#define MAXNODEBIT 256

typedef struct node_addr {
  int n_bytes;			/* number of bytes */
  int n_size;			/* size of node */
  byte n_id[MAXNODEBYTE];	/* node number on that port */
} NODE;

#endif /* FILE INCLUDED */

SHAR_EOF
if test 1353 -ne "`wc -c < 'node.h'`"
then
	echo shar: "error transmitting 'node.h'" '(should have been 1353 characters)'
fi
chmod 440 'node.h'
fi
if test -f 'proto_intf.h'
then
	echo shar: "will not over-write existing file 'proto_intf.h'"
else
cat << \SHAR_EOF > 'proto_intf.h'
/*
 * $Author: cck $ $Date: 88/09/14 10:20:15 $
 * $Header: /src/local/mac/cap/etalk/RCS/proto_intf.h,v 1.3 88/09/14 10:20:15 cck Rel $
 * $Revision: 1.3 $
*/

/*
 * protocol interface header:
 *
 *  Provides ability to read/write packets at ethernet level
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August 1988  CCKim Created
 *
*/

/* call first (double call okay) */
export int pi_setup();
/* call with protocol (network order), device name (e.g. qe), device */
/* unit number, return < 0 on error, > 0 protocol handle */
export int pi_open(/* int protocol, char * dev, int devno */);
/* get ethernet address, ea is pointer to place to return address */
export int pi_get_ethernet_address(/* int edx, u_char *ea */);
/* returns TRUE if interface tap can see its own broadcasts (or they */
/* are delivered by system */
export int pi_delivers_self_broadcasts();
/* close a protocol handle */
export int pi_close(/* int edx */);
/* establishes a listener to be called when data ready on */
/* protocol,interface.  (*listener)(socket, arg, eh) */
export int pi_listener(/* int edx, int (*listener), caddr_t arg */);
/* like read */
export int pi_read(/* int edx, caddr_t buf, int bufsize */);
/* like readv */
export int pi_readv(/* int edx, struct iovec iov[], int iovlen */ );
/* like write */
export int pi_write(/* int idx, caddr_t buf, int bufsize */);
/* like writev */
export int pi_writev(/* int edx, struct iovec iov[], int iovlen */ );

#define EHRD 6			/* ethernet hardware address length */

/* much like struct ether_header, but we know what is here -- can be */
/* variable on systems */
struct ethernet_addresses {
  u_char daddr[EHRD];
  u_char saddr[EHRD];
  u_short etype;
};

#define MAXOPENPROT 10		/* arb. number */

SHAR_EOF
if test 2235 -ne "`wc -c < 'proto_intf.h'`"
then
	echo shar: "error transmitting 'proto_intf.h'" '(should have been 2235 characters)'
fi
chmod 440 'proto_intf.h'
fi
if test -f 'rtmp.c'
then
	echo shar: "will not over-write existing file 'rtmp.c'"
else
cat << \SHAR_EOF > 'rtmp.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/18 15:37:30 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/rtmp.c,v 1.24 88/09/18 15:37:30 cck Rel $";
static char revision[] = "$Revision: 1.24 $";

/*
 * rtmp.c: RTMP, ZIP, and NBP gateway protocol modules
 *
 * dropped NBP here because it needs access to routing table
 *
 * Follows specification set in "Inside Appletalk" by Gursharan Sidhu,
 * Richard F. Andrews, and Alan B. Oppenheimer, Apple Computer, Inc.
 * June 1986.
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 * Edit History:
 *
 *  August, 1988  CCKim Created
 *
*/
static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>
#include "gw.h"
#ifndef OWNHASH
#include <hash.h>
#endif

/* stupid function so we can link together items */
struct chain {
  struct chain *c_next;		/* next in list */
  caddr_t c_data;		/* data */
};
/* should be in appletalk.h */
#define ZIP_query 1		/* query type */
#define ZIP_reply 2		/* reply type */
#define ZIP_takedown 3		/* takedown (NYI) */
#define ZIP_bringup 4		/* bringup (NYI) */

struct zipddp {			/* ZIP packet */
  byte zip_cmd;
  byte zip_netcount;
};

/* zip name as found in zip reply and bringup packets */
struct zipname {
  word zip_net;
  byte zip_len;
};
#define zipNameLen 3

/* route states */
#define R_NONE 0		/* or false */
#define R_GOOD 1
#define R_BAD 2
#define R_SUSPECT 3

/* messages describing route states (shouldn't change) */
private char *rtmp_state_msg[4] = {
  "deleted",
  "good",
  "bad",
  "suspect"
};

#define MAXHOPS 15		/* maximum # of hops a route can have */

/* when to "age" route entries */
#define RTMP_VALIDITY_TIMEOUT 20
/* rtmp send timeout: initial is offset from zip timeout */
#define RTMP_INITIAL_SEND_TIMEOUT  30
#define RTMP_SEND_TIMEOUT 10
/* initial zip is for "local" zones */
#define ZIP_INITIAL_TIMEOUT 5
#define ZIP_QUERY_TIMEOUT 10

/* maximum number of routes */
#define NROUTES 100

private struct route_entry *routes; /* chain of routes */
private caddr_t route_htable_handle; /* route table handle */


/* bridge node handling stuff */
/* for now (should be hash table or some such)  */
private caddr_t bridgenode_htable_handle;
#define NUMBRNODES 100
struct bridge_node {		/* bridge node entry */
  NODE id;
  PORT_T port;
};

struct bridge_node_key {	/* structure to pass a key in */
  NODE *idp;
  PORT_T port;
};

private int zone_unknown = 0;	/* a zone is unknown */

/* same as pstr */
private caddr_t zone_hash_table;
#define NZONES 50		/* random, need not be too big */
private struct chain *zonelist;	/* chain of zones */

private int m_route = 0;
private int m_bnode = 0;
private int m_cnode = 0;
private int m_zone = 0;

export void rtmp_init();
export void rtmp_start();
private int route_compare();
private caddr_t route_alloc();
private u_int route_compress();
private void routes_init();
export struct route_entry *route_find();
private struct route_entry *route_create();
private void route_delete();
export struct route_entry *route_list_start();
export struct route_entry *route_next();
export int route_add_host_entry();
export char *node_format();
private int bstrcmp();
private int bridgenode_compare();
private caddr_t bridgenode_alloc();
private u_int bridgenode_compress();
private void bridgenode_init();
private NODE *bridgenode_find();
private boolean rtmp_handler();
private void rtmp_dump_entry();
private void rtmp_dump_entry_to_file();
private int rtmp_send_timeout();
private void rtmp_send();
private int rtmp_validity_timeout();
private void rtmp_replace_entry();
private void rtmp_update_entry();
private boolean rtmprq_handler();
private boolean zip_handler();
private int zip_query_timeout();
private int zip_query_handler();
private int zip_reply_handler();
private int zip_atp_handler();
/* private int zone_hash(); */
private caddr_t zone_alloc();
private int pstrc();
private int zone_compare();
private u_int zone_compress();
private void zone_init();
export byte *zone_find();

private void rtmp_format_hash_stats();
export void rtmp_dump_stats();
export void rtmp_dump_table();

/*
 * initialize
 * 
 * clear up vars
*/
export void
rtmp_init()
{
  routes_init();
  bridgenode_init();
  zone_init();
}


/*
 * rtmp start - fires up the timers 
 *
*/
export void
rtmp_start()
{
  struct timeval tv;
  tv.tv_sec = RTMP_VALIDITY_TIMEOUT; /* 20 second validity timer */
  tv.tv_usec = 0;
  relTimeout(rtmp_validity_timeout, 0, &tv, TRUE);
  /* these last two could be combined */
  tv.tv_sec = RTMP_INITIAL_SEND_TIMEOUT;
  tv.tv_usec = 0;
  relTimeout(rtmp_send_timeout, 0, &tv, TRUE);
  tv.tv_sec = ZIP_INITIAL_TIMEOUT;
  tv.tv_usec = 0;
  relTimeout(zip_query_timeout, 0, &tv, TRUE);
}

/* routing table handler */
/*
 * compare key net to route entry
 *
*/
private int
route_compare(net,re)
word *net;
struct route_entry *re;
{
  return(((int)*net) - ((int)(re->re_ddp_net)));
}

/*
 * allocate data: create a route
 *
 *
*/
private caddr_t
route_alloc(net)
word *net;
{
  struct route_entry *re;

  if ((re = (struct route_entry *)malloc(sizeof(struct route_entry))) == NULL)
    return(NULL);
  m_route++;
  re->re_ddp_net = *net;	/* copy in network */
  re->re_state = R_NONE;	/* set state to none */
  re->re_next = routes;
  routes = re;			/* link to head */
  return((caddr_t)re);		/* and return */
}

/*
 * compress key to u_int
 *
*/
private u_int
route_compress(net)
word *net;
{
  return(*net);
}

private void
routes_init()
{
  routes = NULL;		/* no routes */
  route_htable_handle =
    h_new(HASH_POLICY_CHAIN, HASH_TYPE_MULTIPLICATIVE, NROUTES,
	  route_compare, route_alloc, route_compress, NULL, NULL, NULL);
  ddp_open(rtmpSkt, rtmp_handler);
}
/*
 * find route for a particular network
 *
*/
export
struct route_entry *
route_find(net)
word net;
{
  struct route_entry *re;
  re = (struct route_entry *)h_member(route_htable_handle, &net);
  if (re && re->re_state)	/* we don't really delete */
    return(re);
  return(NULL);
}

/*
 * create a routing entry and initialize it.
 *
*/
private struct route_entry *
route_create(net)
word net;
{
  register struct route_entry *re;
  int d, b;

  re = (struct route_entry *)
    h_operation(HASH_OP_INSERT, route_htable_handle, &net, -1,-1,&d,&b);

  if (re == NULL)		/* should not happen, but. */
    return(NULL);
  log(LOG_LOTS, "new route for net %d.%d at hash [bkt %d, d %d]\n",
	 nkipnetnumber(net),nkipsubnetnumber(net),
	 b,d);
  zone_unknown++;		/* new route, so set */
  re->re_dist = 0;		/* assume zero distance */
  re->re_bridgeid_p = NULL;	/* means self */
/*  re->re_ddp_net = net; */ /* done already */
  re->re_zip_taken = FALSE;	/* make sure */
  re->re_zonep = NULL;		/* make sure */
  return(re);
}

/* delete route - for now just set state to none */
/* may want to time it out at some point */
private void
route_delete(re)
struct route_entry *re;
{
  re->re_state = R_NONE;
}


/* return route list start */
export struct route_entry *
route_list_start()
{
  return(routes);
}

/* get next in list: hidden in case we want to change way done */
export struct route_entry *
route_next(re)
struct route_entry *re;
{
  return(re->re_next);
}

/*
 * establish a new port
 *
 * net in network format (8 bits - word)
 * node as bytes (variable length, network order, zero padded in front)
 * nodesize - number of bits valid in node
 * ddp_node
 * zone to set if any
 *
*/
export
route_add_host_entry(port, net, zone)
PORT_T port;
word net;
byte *zone;
{
  struct route_entry *re;

  if (net == 0)
    return(-1);
  /* if network given, then construct internal route */
  /* do a find in case route already acquired */
  if ((re = route_find(net)) == NULL)
    if ((re = route_create(net)) == NULL)
      return(-1);
  /* reset or set */
  re->re_state = R_GOOD;
  re->re_port = port;
  re->re_dist = 0;
  re->re_bridgeid_p = NULL;
  log(LOG_BASE, "port %d host route added for network %d.%d",
      port, nkipnetnumber(net), nkipsubnetnumber(net));
  rtmp_dump_entry("host port", re);
  if (zone == NULL)
    return(0);
  /* if zone given for net, then add it */
  re->re_zonep = zone_find(zone, TRUE);	/* insert zone name */
  if (re->re_zonep && zone_unknown > 0) {
    log(LOG_BASE, "port %d zone name %s inserted", port, re->re_zonep+1);
    zone_unknown--;
  }
  return(0);
}



/*
 * format node structure for printing
 *
*/
export char *
node_format(node)
NODE *node;
{
  static char tmpbuf[200];
  static char *fmtstr = "%x%x%x%x%x%x%x%x";
  byte *id;
  int n;

  if (node == NULL)
    return("self");
  id = node->n_id;
  /* if less than 5 bytes, convert to network order int and print */
  switch (node->n_bytes) {
  case 4:
    n = ntohl((id[0]<<24)|(id[1]<<16)|(id[2]<<8)|id[3]);
    break;
  case 3:
    n = ntohl((id[0]<<16)|(id[1]<<8)|id[2]);
    break;
  case 2:
    n = ntohs(id[0]<<8|id[1]);
    break;
  case 1:
    n = id[0];
    break;
  default:
    sprintf(tmpbuf, fmtstr+2*(MAXNODEBYTE-node->n_bytes),
	    node->n_id[0], node->n_id[1],
	  node->n_id[2], node->n_id[3],
	  node->n_id[4], node->n_id[5],
	  node->n_id[6], node->n_id[7]);
    return(tmpbuf);
  }
  sprintf(tmpbuf, "%d", n);
  return(tmpbuf);
}

/* like strncmp, but allows "0" bytes */
private int
bstrcmp(a,b,l)
register byte *a;
register byte *b;
register int l;
{
  register int c = 0;		/* if zero length, then same */

  while (l--) 			/* while data */
    if ((c = (*a++ - *b++)))	/* compare and get difference */
      break;			/* return c */
  return(c);			/* return value */
}

/* bridge node table handler */

/* compare (port,node) to bridgenode */
private int
bridgenode_compare(k, bn)
struct bridge_node_key *k;
struct bridge_node *bn;
{
  if (k->port != bn->port)
    return((int)(k->port - bn->port));
  if (k->idp->n_size != bn->id.n_size)
    return(k->idp->n_size - bn->id.n_size);
  return(bstrcmp((caddr_t)k->idp->n_id, (caddr_t)bn->id.n_id, bn->id.n_bytes));
}

/* allocate space for a bridge node */
private caddr_t
bridgenode_alloc(k)
struct bridge_node_key *k;
{
  struct bridge_node *bn;

  if ((bn = (struct bridge_node *)malloc(sizeof(struct bridge_node))) == NULL)
    return(NULL);
  m_bnode++;
#ifdef DEBUG
  log(0, "BRIDGE NODE CREATE");
  log(0, "PORT %d", k->port);
  log(0, "ID len %d, byte 0 %d", k->idp->n_size, k->idp->n_id[0]);
#endif
  bn->id = *k->idp;		/* copy in */
  bn->port = k->port;
  return((caddr_t)bn);
}

/* compress key  to an u_int */
private u_int
bridgenode_compress(k)
struct bridge_node_key *k;
{
  u_int r = (u_int)k->port;
  int i = k->idp->n_bytes;	/* # of bytes */
  byte *p = k->idp->n_id;	/* data */
    
  /* add in p, but keep rotating r */
  r ^= k->idp->n_size;		/* xor size in */
  while (i--)
    r = ((r>>1)|(r<<31)) + *p++;
  return(r);
}


/* initialize */
/* should we limit the # of bridge nodes by using a open hash? */
private void
bridgenode_init()
{
  bridgenode_htable_handle =
    h_new(HASH_POLICY_CHAIN, HASH_TYPE_MULTIPLICATIVE, NUMBRNODES,
	  bridgenode_compare, bridgenode_alloc,
	  bridgenode_compress, NULL, NULL, NULL);
}

/* find a bridge node based on port,node key */
private NODE *
bridgenode_find(port, node)
PORT_T port;
NODE *node;
{
  struct bridge_node *bn;
  struct bridge_node_key bk;
  int d,b;

  bk.idp = node;
  bk.port = port;

  bn = (struct bridge_node *)
    h_operation(HASH_OP_INSERT, bridgenode_htable_handle, &bk, -1,-1,&d,&b);

  if (bn == NULL)
    return(NULL);
#ifdef DEBUG
  printf("look bridge node %s at hash [bkt %d, d %d]\n",
	 node_format(node), b,d);
#endif
  return(&bn->id);		/* return node id */
}

/* RTMP handling */

/*
 * handle incoming rtmp packets
 *
*/
/*ARGSUSED*/
private boolean
rtmp_handler(port, ddp, data, len)
PORT_T port;
DDP *ddp;			/* not used */
byte *data;
int len;
{
  struct route_entry *re;
  RTMPtuple tuple;
  word net;
  NODE id, *sid;

  if (ddp->type == ddpRTMPRQ)	/* is it a rtmp request? */
    return(rtmprq_handler(port,ddp)); /* yes, handle it */
  if (ddp->type != ddpRTMP)	/* is it rtmp? */
    return(TRUE);		/* no, dump it */
  if (len < sizeof(net))	/* rtmpSize */
    return(TRUE);
  /* get net out */
  bcopy((caddr_t)data, (caddr_t)&net, sizeof(net));
  len -= sizeof(net);
  data += sizeof(net);
  if (len < 1)			/* id len */
    return(TRUE);
  id.n_size = *data;		/* get id length */
  id.n_bytes = (id.n_size + 7) / 8; /* make into bytes */
  if (len < (id.n_bytes+1))	/* id len + id */
    return;
  bcopy((caddr_t)data+1, (caddr_t)id.n_id, id.n_bytes);	/* copy id */
  len -= (id.n_bytes + 1);	/* reduce */
  data += (id.n_bytes + 1);	/* reduce */
  
  sid = bridgenode_find(port, &id); /* canonicalize */
  if (sid == NULL)		/* ourselves or no room */
    return(TRUE);
  log(LOG_BASE, "NEW RTMP: port %d, source %s", port, node_format(sid));

  if (!PORT_NET_READY(port, net, sid))
    route_add_host_entry(port, net, NULL); /* zone isn't known yet! */

  /* use tuplesize because of byte alignment problems */
  while (len >= rtmpTupleSize) {
    bcopy((caddr_t)data, (caddr_t)&tuple, rtmpTupleSize);
    data += rtmpTupleSize, len -= rtmpTupleSize;
    re = route_find(tuple.net); /* get entry if any */
    if (re) 			/* update */
      rtmp_update_entry(re, port, sid, &tuple);
    else { 			/* create */
      re = route_create(tuple.net);
      if (!re)
	continue;
      log(LOG_LOTS, "create_entry: net %d.%d",
	       nkipnetnumber(tuple.net),
	       nkipsubnetnumber(tuple.net));
      rtmp_replace_entry(re, port, sid, &tuple, FALSE);
    }
  }
  return(TRUE);
}


/*
 * dump rtmp table entry nicely
 *
*/
private void
rtmp_dump_entry(msg, re)
char *msg;
struct route_entry *re;
{
  if (!re->re_state)
    return;
  /* fixup */
  log(LOG_LOTS, "%s: net %d.%d, bridge %s, dist %d, port %d, state %s",
      msg, nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net),
      node_format(re->re_bridgeid_p), re->re_dist, re->re_port,
      rtmp_state_msg[re->re_state]);
}

private void
rtmp_dump_entry_to_file(fd, re)
FILE *fd;
struct route_entry *re;
{
  if (!re->re_state)
    return;
  /* fixup */
fprintf(fd, " net %d.%d, bridge %s, dist %d, port %d, state %s, zone %d-%s\n",
      nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net),
      node_format(re->re_bridgeid_p), re->re_dist, re->re_port,
      rtmp_state_msg[re->re_state], 
      (re->re_zonep ? *re->re_zonep : 0),
      (re->re_zonep ? ((char *)re->re_zonep+1) : (char *)"<unknown>"));
}

/*
 * timeout: rtmp send - broadcast rtmp's
 * 
*/
private int
rtmp_send_timeout()
{
  PORT_T port;
  register struct route_entry *re;
  struct timeval tv;
  RTMP *rtmp;
  RTMPtuple tuple;
  char buf[ddpMaxData];
  char *p;
  int count, rest;
  
  rtmp = (RTMP *)buf;
  re = routes;
  /* load up rtmp entry */
  do {
    p = buf + sizeof(RTMP);	/* point into buf */
    rest = ddpMaxData - sizeof(RTMP);
    /* while room in packet */
    for (count = 0; re && rest >= rtmpTupleSize; re = re->re_next) {
      if (re->re_state != R_GOOD && re->re_state != R_SUSPECT)
	continue;		/* not good or suspect */
      if (re->re_zip_taken)	/* in zip takedown */
	continue;
      /* strict: don't send out updates for routes we don't know */
      /* the zone for -- this way bad data may go away */
      if (re->re_zonep == NULL)
	continue;
      if (!(re->re_dist < MAXHOPS))
	continue;
      tuple.net = re->re_ddp_net;
      tuple.hops = re->re_dist;
      bcopy((caddr_t)&tuple, p, rtmpTupleSize);
      count++;			/* found another */
      p += rtmpTupleSize;
      rest -= rtmpTupleSize;
    }
    for (port = PORT_LIST_START(); port != NULL; port = PORT_NEXT(port)) {
      NODE *pid;

      if (!PORT_ISBRIDGING(port))
	continue;
      if (!PORT_READY(port))
	continue;
      rtmp->net = PORT_DDPNET(port);
      pid = PORT_NODEID(port);
      rtmp->idLen = pid->n_size;
      bcopy((caddr_t)pid->n_id, (caddr_t)&rtmp->id, pid->n_bytes);
      rtmp_send(rtmp, 3+pid->n_bytes, count, rtmp->net, DDP_BROADCAST_NODE);
    }
  } while (re);			/* still routes */
  tv.tv_sec = RTMP_SEND_TIMEOUT; /* 10 second send timer */
  tv.tv_usec = 0;
  relTimeout(rtmp_send_timeout, 0, &tv, TRUE);
}

/*
 * send the rtmp packet on the specified port
 *
*/
private void
rtmp_send(rtmp, rtmp_size, count, dstnet, dst)
RTMP *rtmp;
int rtmp_size;
int count;
word dstnet;
byte dst;
{
  DDP rddp;		/* reply ddp header */
  int dlen = rtmp_size+rtmpTupleSize*count;

  rddp.srcSkt = rtmpSkt;
  rddp.dstNet = dstnet;
  rddp.dstNode = dst;
  rddp.dstSkt = rtmpSkt;
  rddp.type = ddpRTMP;
  ddp_output(NULL, &rddp, rtmp, dlen);
}

/*
 * timeout: rtmp validity
 *
 * run timer on rtmp validity
*/
private int
rtmp_validity_timeout()
{
  register struct route_entry *re;
  struct timeval tv;
  for (re = routes; re; re = re->re_next) {
    switch (re->re_state) {
    case R_GOOD:
      if (re->re_dist != 0)
	re->re_state = R_SUSPECT;
      break;
    case R_SUSPECT:
      rtmp_dump_entry("route went bad", re);
      re->re_state = R_BAD;
      break;
    case R_BAD:
      rtmp_dump_entry("route deleted", re);
      route_delete(re);
      break;
    }
  }
  tv.tv_sec = RTMP_VALIDITY_TIMEOUT; /* 20 second validity timer */
  tv.tv_usec = 0;

  relTimeout(rtmp_validity_timeout, 0, &tv, TRUE);
}

/*
 * rtmp_replace_entry: replace or add a route
 *
 * if istickler is set then this is a tickler packet that ensures
 * route stays good
 *
*/
private void
rtmp_replace_entry(re, port, sid, tuple, istickler)
struct route_entry *re;
PORT_T port;			/* source port */
NODE *sid;			/* source id */
RTMPtuple *tuple;
int istickler;			/* true if this replace should be */
				/* considered "a tickle" */
{
  int rewasthere = re->re_state;

  /* dump won't do anything if no state */
  if (rewasthere && !istickler)
    rtmp_dump_entry("replacing entry", re);
  re->re_dist = tuple->hops + 1;
  re->re_bridgeid_p = sid;
  re->re_port = port;
  re->re_state = R_GOOD;
  if (!istickler)
    rtmp_dump_entry(rewasthere ? "replaced entry" : "new" , re);
}

/*
 * rtmp_update_entry - figure out whether the route should be updated 
 *  or not
 *
*/
private void
rtmp_update_entry(re, port, sid,  tuple)
struct route_entry *re;
PORT_T port;			/* source port */
NODE *sid;			/* source id */
RTMPtuple *tuple;
{
  if (re->re_state == R_BAD && tuple->hops < MAXHOPS) { /* replace entry */
    log(LOG_LOTS, "update_entry: net %d.%d, replacing because bad", 
	nkipnetnumber(tuple->net),
	nkipsubnetnumber(tuple->net));
    rtmp_replace_entry(re, port, sid, tuple, FALSE);
    return;
  }
  if (tuple->hops < MAXHOPS && re->re_dist > tuple->hops) {
    int istickler; 
    istickler = (re->re_dist == (tuple->hops+1));
    if (!istickler) {
      /* if not simple case of updating bad point */
      log(LOG_LOTS, "update_entry: net %d.%d, replacing because better route",
	  nkipnetnumber(tuple->net),
	  nkipsubnetnumber(tuple->net));
    }
    rtmp_replace_entry(re, port, sid, tuple, istickler);
    return;
  }
  /* know we know that hops >= 15 or re->re_dist <= tuple->hops */
  /* if re's bridge matches the rmtp source bridge */
  /* and in on the same port, then the network is futher away... */
  if (re->re_bridgeid_p == sid && re->re_port == port) {
    re->re_dist++;
    if ((re->re_dist) <= MAXHOPS) {
      re->re_state = R_GOOD;
      rtmp_dump_entry("hop count increased", re);
    } else {
      rtmp_dump_entry("too many hops", re);
      route_delete(re);
    }
  }
}


/*
 * handle incoming rtmp request packet
 *
*/
private int
rtmprq_handler(port, ddp)
PORT_T port;
DDP *ddp;
{
  RTMP rtmp;
  NODE *pid;

  if (!PORT_ISBRIDGING(port))	/* not full bridge */
    return(TRUE);		/* so, don't advertise */
  if (!PORT_READY(port))	/* port isn't fully setup */
    return(TRUE);
  /* respond with data about the port */
  rtmp.net = PORT_DDPNET(port);
  pid = PORT_NODEID(port);
  rtmp.idLen = pid->n_size;
  bcopy((caddr_t)pid->n_id, (caddr_t)&rtmp.id, pid->n_bytes);
  /* no tuples */
  rtmp_send(&rtmp, 3+pid->n_bytes, 0, ddp->srcNet, ddp->srcNode);
  return(TRUE);
}

/*
 * handle incoming DDP zip packets 
*/
private boolean
zip_handler(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  struct zipddp zd;

  if (ddp->type == ddpATP)	/* atp? */
    return(zip_atp_handler(port, ddp, data, datalen)); /* yes */

  if (ddp->type != ddpZIP)	/* zip? */
    return(TRUE);		/* no, dump it */

  if (datalen < sizeof(zd))
    return(TRUE);
  bcopy((caddr_t)data, (caddr_t)&zd, sizeof(zd)); /* get zip header */
  datalen -= sizeof(zd), data += sizeof(zd);

  switch (zd.zip_cmd) {
  case ZIP_query:
    zip_query_handler(zd.zip_netcount, port, ddp, data, datalen);
    break;
  case ZIP_reply:
    zip_reply_handler(zd.zip_netcount, port, ddp, data, datalen);
    break;
  case ZIP_takedown:
    break;
  case ZIP_bringup:
    break;
  }
  return(TRUE);
}

/*
 * ZIP timeout
 *
 * query routes with unkown zones
 *
 *  algorithm: send zip query to bridge that sent us the rtmp.
 *  try to enclose as many networks as possible in the query
 *
*/
private int
zip_query_timeout()
{
  DDP ddp;
  struct route_entry *re;
  struct route_entry *first;
  PORT_T port;
  int first_idx;
  int j;
  NODE *curbridge;
  struct zipddp *zd;
  word zip_buf[ddpMaxData / sizeof(word)];
  int count;
  int mainnotknown = FALSE;

  if (!zone_unknown)		/* set whenever new route is created */
    return;
  /* initialize helper field, find first to query */
  for (first=NULL, re = routes, j = 0; re ; re = re->re_next)
    if (re->re_state && !re->re_zonep) {
      if (!first) {		/* remember first */
	first = re;
      }
      if (re->re_bridgeid_p == NULL) {
	if (!mainnotknown) {
	  first = re;		/* reset first one to do */
	  mainnotknown = TRUE;	/* don't know zone of a main interface */
	}
      }
      re->re_zip_helper = 0;
      j++;			/* mark work */
    }
  if (j == 0)
    zone_unknown = FALSE;

  /* query the various bridges */
  while (j && first) {
    curbridge = first->re_bridgeid_p; /* bridge id pointer */
    port = first->re_port;	/* get port for this bridge */
    count = 1;			/* skip first word */
    re = first;			/* this is where to start */
    first = NULL;		/* reset start point */
    for (; re ;  re = re->re_next) {
      /* skip if main interf. not known */
      if (mainnotknown && re->re_bridgeid_p) 
	continue;		/* and not local interface */
      if (!re->re_state || re->re_zonep || re->re_zip_helper) /* ignore */
	continue;
      if (re->re_bridgeid_p == curbridge &&
	  (count < (ddpMaxData / sizeof(word)))) {
	j--;
	re->re_zip_helper = 1;	/* mark  */
	zip_buf[count++] = re->re_ddp_net; /* to get */
	log(LOG_LOTS, "will zip %s for %d.%d", node_format(curbridge), 
	    nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net));
      } else if (!first) {	/* remember next in sequence */
	  first = re;		/* set first */
	}
    }
    if (count == 1)		/* something wierd happened */
      continue;
    zd = (struct zipddp *)zip_buf;
    zd->zip_cmd = ZIP_query;
    zd->zip_netcount = count - 1;
    ddp.dstNet = PORT_DDPNET(port);
    /* either don't care (because will be short ddp) or broadcast */
    /* actually need reverse mapping (port,node) to ddp node */
    ddp.dstNode = curbridge ? 0 : DDP_BROADCAST_NODE;
    ddp.dstSkt = zipZIS;
    ddp.srcSkt = zipZIS;
    ddp.type = ddpZIP;
    log(LOG_LOTS, "zipping %s for %d networks", node_format(curbridge),
	count-1);
    ddp_output(curbridge, &ddp, zip_buf, count*sizeof(word));
  }
  /* restart query */
  { struct timeval tv; 
    tv.tv_sec = ZIP_QUERY_TIMEOUT; 
    tv.tv_usec = 0;

    relTimeout(zip_query_timeout, 0, &tv, TRUE);
  }
}


/*
 * zip_query_handler: handle an incoming zip query packet
 *
*/
private int
zip_query_handler(count, port, ddp, data, datalen)
int count;
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  struct route_entry *re;
  DDP sddp;
  byte buf[ddpMaxData];
  int slen, i, zl;
  byte *p;
  word net;
  struct zipddp *zd;

  p = buf;
  p += sizeof(struct zipddp);
  slen = sizeof(struct zipddp);
  zd = (struct zipddp *)buf;
  zd->zip_cmd = ZIP_reply;	/* set command */
  zd->zip_netcount = 0;		/* zero nets in response as yet */
  /* best effort -- fit as many as possible, but don't bother with */
  /* multiple replies -- not clear remote would handle anyway */
  while (count--) {
    if (datalen < sizeof(net))	/* any data left? */
      break;			/* no, count is wrong, stop! */
    bcopy((caddr_t)data, (caddr_t)&net, sizeof(net));
    datalen -= sizeof(net), data += sizeof(net);
    if ((re = route_find(net)) == NULL)	/* no route skip */
      continue;
    if (!re->re_zonep)
      continue;
    i = ((*re->re_zonep) + 1 + sizeof(word));
    if ((slen + i) > ddpMaxData)
      break;
    /* copy in response data */
    bcopy((caddr_t)&net, (caddr_t)p, sizeof(net));
    p += sizeof(net);
    zl = *re->re_zonep + 1;		/* get zone length */
    bcopy((caddr_t)re->re_zonep, (caddr_t)p, zl); /* copy zone name */
    p += zl;
    zd->zip_netcount++;		/* increment count */
    log(LOG_JUNK, "query on net %d.%d yields zone %s", 
	nkipnetnumber(net), nkipsubnetnumber(net), re->re_zonep+1);
    slen += i;
  }
  sddp.dstNet = ddp->srcNet;
  sddp.dstNode = ddp->srcNode;
  sddp.dstSkt = ddp->srcSkt;
  sddp.srcSkt = zipZIS;
  sddp.type = ddpZIP;
  ddp_output(NULL, &sddp, buf, slen);
}

/*
 * handle incoming zip reply.  basically insert zone names
 * into the table if possible
 *
*/
private int
zip_reply_handler(count, port, ddp, data, datalen)
int count;
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  word net;
  struct route_entry *re;
  byte *p = data;
  byte *pp, *zone;
  int zonelen;

  while (count--) {
    if (datalen < (1+sizeof(net)))
      break;
    bcopy((caddr_t)p, (caddr_t)&net, sizeof(net)); /* get zone information */
    p += sizeof(net);		/* move to the name */
    datalen -= sizeof(net);
    zonelen = 1 + *p;
    /* now p points to a pstr */
    if (datalen < zonelen)	/* no data left? */
      break;
    zone = p;			/* p now points to zone */
    p += zonelen;
    datalen -= zonelen;
    if ((re = route_find(net)) == NULL)
      continue;
    pp = (byte *)zone_find(zone, TRUE); /* find or insert zone name */
    if (pp == NULL) {
      log(LOG_BASE, "ZIP: no room for insert for zone\n");
      continue;
    }
    if (re->re_zonep) {		/* zone already known for net */
      if (pp && pp != re->re_zonep) {
	log(LOG_BASE, "zone name conflict for %d.%d, received %s had %s\n",
	    nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net),
	    pp+1, re->re_zonep+1);
      }
      continue;
    }
    /* must have zone for this route (which didn't have one before) */
    re->re_zonep = pp;	/* mark zone */
    /* if zone is known for primary route, say so */
    if (re->re_bridgeid_p == NULL && re->re_ddp_net == PORT_DDPNET(port))
      PORT_ZONE_KNOWN(port, re->re_zonep);
    log(LOG_BASE, "ZIPPED: from %d.%d.%d network %d.%d for zone %s",
	nkipnetnumber(ddp->srcNet), nkipsubnetnumber(ddp->srcNet),
	ddp->srcNode,
	nkipnetnumber(re->re_ddp_net), nkipsubnetnumber(re->re_ddp_net),
	re->re_zonep+1);
  }
}

/*
 * handle a zip atp command: Get Zone List or Get My Zone 
 *
 *
*/
/* these are only defined in abatp.h not appletalk.h (because nobody) */
/* should need such fine control under normal cirucmstances */
/* put the ifndefs around in case we decide to move them one day */
#ifndef atpCodeMask
# define atpCodeMask 0xc0
#endif
#ifndef atpReqCode
# define atpReqCode 0x40
#endif
#ifndef atpRspCode
# define atpRspCode 0x80
#endif
#ifndef atpEOM
# define atpEOM 0x10
#endif

private int
zip_atp_handler(port, ddp, data, datalen)
PORT_T port;
DDP *ddp;
byte *data;
int datalen;
{
  int paranoia = FALSE;
  DDP sddp;
  ATP *atp;			/* pointer to atp header */
  zipUserBytes *zub;		/* pointer to zip user byte */
  char data_buf[ddpMaxData];	/* data buffer */
  char *p;			/* pointer to data */
  int ps = ddpMaxData;		/* room left in buffer */
  int sidx;			/* for getzonelist */
  int count, t, i;
  struct chain *zp;

  if (datalen < sizeof(ATP))
    return;
  bcopy((caddr_t)data, (caddr_t)data_buf, sizeof(ATP)); /* get bytes */
  atp = (ATP *)data_buf;	/* should be aligned */
  p = data_buf + sizeof(ATP);	/* point to data */
  ps -= sizeof(ATP);
  /* control must hold request and only request */
  if ((atp->control & atpCodeMask) != atpReqCode)
    return;
  /* bitmap should ask for at least one packet */
  if ((atp->bitmap & 0x1) == 0)
    return;
  zub = (zipUserBytes *)&atp->userData;
  if (paranoia && zub->zip_zero != 0)
    return;
  zub->zip_zero = 0;		/* ensure */
  switch (zub->zip_cmd) {
  case zip_GetMyZone:
    if (paranoia && ntohs(zub->zip_index) != 0)
      return;
    count = 1;
    zub->zip_cmd = 0;		/* zero because gmz */
    /* return zone of source network */
    /* if given network is 0 (mynet), use that of port */
    { struct route_entry *re;
      if (ddp->srcNet == 0) {
	if ((re = route_find(PORT_DDPNET(port))) == NULL)
	  return;
      } else {
	if ((re = route_find(ddp->srcNet)) == NULL)
	  return;
      }
      if (re->re_zonep == NULL)
	return;
      /* no way we could fill up buffer */
      { int zl;
	zl = 1 + *re->re_zonep;
	bcopy((caddr_t)re->re_zonep, (caddr_t)p, zl);
	ps -= zl;
      }
    }
    break;
  case zip_GetZoneList:
    sidx = ntohs(zub->zip_index);
    sidx--;			/* 1 is start */
    /* move through zonelist, decrementing sidx as we find a filled slot */
    /* a zone name may be sent more than once if we get an incoming zone */
    /* between GZL commands */
    /* move through sidx items */
    for (zp = zonelist; zp && sidx ; zp = zp->c_next)
      sidx--;
    if (sidx)			/* no more zones */
      break;
    /* i already set, zp already set */
    /* assume LastFlag */
    zub->zip_cmd = 0xff;	/* set every bit because not sure */
				/* which bit is lastflag */
    count = 0;
    while (zp) {
      byte *znp = (byte *)zp->c_data; /* get zone name */
      t = znp[0] + 1;		/* get length of zone name */
      if ((ps - t) < 0) {
	zub->zip_cmd = 0;	/* clear lastflag: one remains */
	break;
      }
      bcopy((caddr_t)znp, (caddr_t)p, t); /* copy data */
      count++;			/* bump count */
      ps -= t;			/* reduce available data */
      p += t;			/* move data pointer */
      zp = zp->c_next;		/* move to next zone */
    }
    zub->zip_index = count;	/* set count */
    break;
  default:			/* bad type, this is NOT paranoia */
    return;
  }
  zub->zip_index = htons(count); /* set count */
  atp->control = atpRspCode|atpEOM;
  atp->bitmap = 0;		/* sequence 0 */
  /* tid already set */
  sddp.dstNet = ddp->srcNet;
  sddp.dstNode = ddp->srcNode;
  sddp.dstSkt = ddp->srcSkt;
  sddp.srcSkt = ddp->dstSkt;
  sddp.type = ddpATP;
  ddp_output(NULL, &sddp, (byte *)data_buf, ddpMaxData - ps);
}

/* keep zone name in a linked list */

/* take a zone pstring and duplicate it -- make sure null terminated */
private caddr_t
zone_alloc(p)
byte *p;
{
  int len = (int)*p;		/* get length */
  struct chain *cnode;
  byte *r;

  if ((cnode = (struct chain *)malloc(sizeof(struct chain))) == NULL)
    return(NULL);
  m_cnode++;
  if (p == NULL)		/* translate NULL string */
    p = '\0';
  r = (byte *)malloc(len+2);	/* one for null, one for lenth */
  if (r == NULL) {
    free(cnode);
    return(NULL);
  }
  m_zone++;
  bcopy(p, r, len+1);		/* copy in data */
  r[len+1] = '\0';		/* make sure tied off */
  cnode->c_next = zonelist;	/* link next to head */
  zonelist = cnode;		/* link link to this */
  cnode->c_data = (caddr_t)r;	/* copy in data */
  return((caddr_t)cnode);
}

private int
pstrc(p,s)
byte *p, *s;
{
  int r = (*p - *s);
  if (r)
    return(r);
  return(bstrcmp(p+1, s+1, *p));
}

private int
zone_compare(s,cnode)
byte *s;
struct chain *cnode;
{
  return(pstrc(s, cnode->c_data));
}

private u_int
zone_compress(p)
byte *p;
{
  u_int r = 0;
  int i = (int) *p++;
  /* add in p, but keep rotating r */
  while (i--)
    r = ((r>>1)|(r<<31)) + *p++;
  return(r);
}

private void
zone_init()
{
  zone_hash_table = h_new(HASH_POLICY_CHAIN, HASH_TYPE_MULTIPLICATIVE,
			  NZONES, zone_compare, zone_alloc, zone_compress,
			  NULL, NULL, NULL);
  zonelist = NULL;
  ddp_open(zipZIS, zip_handler);
}

export byte *
zone_find(name, insert)
byte *name;
int insert;
{
  int b, d;
  struct chain *cnode;
  cnode = (struct chain *)h_operation(insert ? HASH_OP_INSERT : HASH_OP_MEMBER,
		      zone_hash_table, name, -1,-1,&d,&b);
  if (cnode == NULL)
    return(NULL);
  log(LOG_LOTS, "%s for %s [%d,%d]\n",
      insert ? "insert" : "lookup", cnode->c_data+1, b,d);
  return((byte *)cnode->c_data); /* return data */
}


private void
rtmp_format_hash_stats(fd, s)
FILE *fd;
struct hash_statistics *s;
{
  fprintf(fd, "\t%d lookups since last rehash, average distance %.02f\n",
	  s->hs_lnum, s->hs_lnum ? ((float)s->hs_lsum / s->hs_lnum) : 0.0);
  fprintf(fd, "\t%d lookups total, average distance %.02f\n",
	  s->hs_clnum, s->hs_clnum ? ((float)s->hs_clsum / s->hs_clnum) : 0.0);
}

export void
rtmp_dump_stats(fd)
FILE *fd;
{
  putc('\n', fd);
  fprintf(fd, "Hash table statistics for zone lookups\n");
  rtmp_format_hash_stats(fd, h_statistics(zone_hash_table));
  fprintf(fd, "\nHash table statistics for routing table lookups\n");
  rtmp_format_hash_stats(fd, h_statistics(route_htable_handle));
  putc('\n', fd);		/* output cr */
  fprintf(fd,"%d routes, %d bridge nodes allocated\n", m_route, m_bnode);
  fprintf(fd,"%d zones, %d zone chain nodes allocated\n", m_zone, m_cnode);
  putc('\n', fd);		/* output cr */
}

export void
rtmp_dump_table(fd)
FILE *fd;
{
  register struct route_entry *re;

  fprintf(fd, "Routing table dump\n");
  for (re = routes; re ; re = re->re_next) 
    if (re->re_state)
      rtmp_dump_entry_to_file(fd, re);
  putc('\n', fd);
}
SHAR_EOF
if test 34333 -ne "`wc -c < 'rtmp.c'`"
then
	echo shar: "error transmitting 'rtmp.c'" '(should have been 34333 characters)'
fi
chmod 440 'rtmp.c'
fi
if test -f 'snitp.c'
then
	echo shar: "will not over-write existing file 'snitp.c'"
else
cat << \SHAR_EOF > 'snitp.c'
static char rcsid[] = "$Author: cck $ $Date: 88/09/14 10:20:35 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/snitp.c,v 1.9 88/09/14 10:20:35 cck Rel $";
static char revision[] = "$Revision: 1.9 $";

/*
 * snitp.c - Simple "protocol" level interface to Streams based NIT
 * (SunOS 4.0)
 *
 *  Provides ability to read/write packets at ethernet level
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Edit History:
 *
 *  July 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/nit.h>
#include <net/nit_if.h>
#include <net/nit_pf.h>
#include <net/nit_buf.h>
#include <net/packetfilt.h>
#include <stropts.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netdb.h>
#include <ctype.h>

#include <netat/appletalk.h>
#include "proto_intf.h"

typedef struct ephandle {	/* ethernet protocol driver handle */
  int inuse;			/* true if inuse */
  int socket;			/* file descriptor of socket */
  struct ifreq ifr;
  int protocol;			/* ethernet protocol */
} EPHANDLE;

private inited = FALSE;

private EPHANDLE ephlist[MAXOPENPROT];


/*
 * setup for particular device devno
 * all pi_open's will go this device
*/
export
pi_setup()
{
  int i;

  if (!inited) {
    for (i = 0 ; i < MAXOPENPROT; i++)
      ephlist[i].inuse = FALSE;
    (void)init_fdlistening();
    inited = TRUE;		/* don't forget now */
  }
  return(TRUE);
}

/*
 * Open up a protocol handle:
 *   user level data:
 *      file descriptor
 *      protocol
 * 
 *   returns -1 and ephandle == NULL if memory allocation problems
 *   returns -1 for other errors
 *   return edx > 0 for okay
*/
export int
pi_open(protocol, dev, devno)
int protocol;
char *dev;
int devno;
{
  struct ephandle *eph;
  char devnamebuf[100];		/* room for device name */
  int s;
  int i;

  for (i = 0; i < MAXOPENPROT; i++) {
    if (!ephlist[i].inuse)
      break;
  }
  if (i == MAXOPENPROT)
    return(0);			/* nothing */
  eph = &ephlist[i];		/* find handle */

  sprintf(devnamebuf, "%s%d",dev,devno);
  strncpy(eph->ifr.ifr_name, devnamebuf, sizeof eph->ifr.ifr_name);
  eph->ifr.ifr_name[sizeof eph->ifr.ifr_name - 1] = ' ';

  if ((s = init_nit(1024, protocol, &eph->ifr)) < 0) {
    return(-1);
  }

  eph->inuse = TRUE;
  eph->socket = s;
  eph->protocol = protocol;
  return(i+1);			/* skip zero */
}

/* returns TRUE if machine will see own broadcasts */
export int
pi_delivers_self_broadcasts()
{
  return(FALSE);
}

export int
pi_close(edx)
int edx;
{
  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);
  fdunlisten(ephlist[edx-1].socket); /* toss listener */
  close(ephlist[edx-1].socket);
  ephlist[edx-1].inuse = 0;
  return(0);
}

/*
 * Initialize nit on a particular protocol type
 * 
 * Runs in promiscous mode for now.
 *
 * Return: socket if no error, < 0 o.w.
*/
private int
init_nit(chunksize, protocol, ifr)
u_long chunksize;
u_short protocol;
struct ifreq *ifr;
{
  u_long if_flags;
  struct strioctl si;
  int s;
  
  /* get clone */
  if ((s = open("/dev/nit", O_RDWR)) < 0) {
    perror("open: /dev/nit");
    return(-1);
  }
  
  /* set up messages */
  if (ioctl(s, I_SRDOPT, (char *)RMSGD) < 0) { /* want messages */
    perror("ioctl: discretem");
    return(-1);
  }
  
  si.ic_timout = INFTIM;
  
  if (setup_pf(s, protocol) < 0)
    return(-1);
#define NOBUF
#ifndef NOBUF
  setup_buf(s, chunksize);
#endif  
  /* bind */
  si.ic_cmd = NIOCBIND;		/* bind */
  si.ic_timout = 10;
  si.ic_len = sizeof(*ifr);
  si.ic_dp = (caddr_t)ifr;
  if (ioctl(s, I_STR, (caddr_t)&si) < 0) {
    perror(ifr->ifr_name);
    return(-1);
  }
  
  /* flush read queue */
  ioctl(s, I_FLUSH, (char *)FLUSHR);
  return(s);
}

#ifndef NOBUF
/*
 * setup buffering (not wanted)
 *
*/
setup_buf(s, chunksize)
int s;
u_long chunksize;
{
  struct strioctl si;
  struct timeval timeout;

  /* Push and configure the buffering module. */
  if (ioctl(s, I_PUSH, "nbuf") < 0) {
    perror("ioctl: nbuf");
  }
  timeout.tv_sec = 0;
  timeout.tv_usec = 200;
  si.ic_cmd = NIOCSTIME;
  si.ic_timout = 10;
  si.ic_len = sizeof timeout;
  si.ic_dp = (char *)&timeout;
  if (ioctl(s, I_STR, (char *)&si) < 0) {
    perror("ioctl: timeout");
    return(-1);
  }
  
  si.ic_cmd = NIOCSCHUNK;
  
  si.ic_len = sizeof chunksize;
  si.ic_dp = (char *)&chunksize;
  if (ioctl(s, I_STR, (char *)&si)) {
    perror("ioctl: chunk size");
    return(-1);
  }
}
#endif

/*
 * establish protocol filter
 *
*/
private int
setup_pf(s, prot)
int s;
u_short prot;
{
  u_short offset;
  struct ether_header eh;
  struct packetfilt pf;
  register u_short *fwp = pf.Pf_Filter;
  struct strioctl si;
#define s_offset(structp, element) (&(((structp)0)->element))
  offset = ((int)s_offset(struct ether_header *, ether_type))/sizeof(u_short);
  *fwp++ = ENF_PUSHWORD + offset;
  *fwp++ = ENF_PUSHLIT | ENF_EQ;
  *fwp++ = htons(prot);
  pf.Pf_FilterLen = 3;

  si.ic_cmd = NIOCSETF;
  si.ic_timout = 10;
  si.ic_len = sizeof(pf);
  si.ic_dp = (char *)&pf;
  if (ioctl(s, I_PUSH, "pf") < 0) {
    perror("ioctl: push protocol filter");
    return(-1);
  }
  if (ioctl(s, I_STR, (char *)&si) < 0) {
    perror("ioctl: protocol filter");
    return(-1);
  }
  return(0);
}

export int
pi_get_ethernet_address(edx,ea)
int edx;
u_char *ea;
{
  struct sockaddr *sa;
  struct ephandle *eph;

  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);
  
  eph = &ephlist[edx-1];
  if (ioctl(eph->socket, SIOCGIFADDR, &eph->ifr) < 0) {
    perror("ioctl: SIOCGIFADDR");
    return(-1);
  }
  sa = (struct sockaddr *)eph->ifr.ifr_data;
  bcopy(sa->sa_data, ea, 6);
  return(0);
}

export
pi_listener(edx, listener, arg)
int edx;
int (*listener)();
caddr_t arg;
{
  if (edx < 1 || edx > MAXOPENPROT || !ephlist[edx-1].inuse)
    return(-1);

  fdlistener(ephlist[edx-1].socket, listener, arg, edx);
}

/*
 * cheat - iov[0] == struct etherheader
 *
*/
export int
pi_readv(edx, iov, iovlen)
int edx;
struct iovec *iov;
int iovlen;
{
  struct ephandle *eph ;
  int cc;

  if (edx < 1 || edx > MAXOPENPROT)
    return(-1);
  eph = &ephlist[edx-1];
  if (!eph->inuse)
    return(-1);
  if ((cc = readv(eph->socket, iov, iovlen)) < 0) {
    perror("abread");
    return(cc);
  }
  return(cc);
}

export int
pi_read(edx, buf, bufsiz)
int edx;
caddr_t buf;
int bufsiz;
{
  struct iovec iov[2];
  struct ethernet_addresses ea;
  int cc;

  iov[0].iov_base = (caddr_t)&ea;
  iov[0].iov_len = sizeof(ea);
  iov[1].iov_base = (caddr_t)buf;
  iov[1].iov_len = bufsiz;
  cc = pi_readv(edx, iov, 2);
  return(cc - sizeof(ea));
}

export int
pi_write(edx, buf, buflen, eaddr)
int edx;
caddr_t buf;
int buflen;
char *eaddr;
{
  struct ephandle *eph;
  struct ether_header *eh;
  struct strbuf pbuf, dbuf;
  struct sockaddr sa;

  if (edx < 1 || edx > MAXOPENPROT || eaddr == NULL)
    return(-1);

  eph = &ephlist[edx-1];
  if (!eph->inuse)
    return(-1);

  sa.sa_family = AF_UNSPEC;	/* by def. */
  eh = (struct ether_header *)sa.sa_data;		/* make pointer */
  bcopy(eaddr, &eh->ether_dhost, sizeof(eh->ether_dhost));
  eh->ether_type = eph->protocol;
  pbuf.len = sizeof(sa);
  pbuf.buf = (char *)&sa;
  dbuf.len = buflen;
  dbuf.buf = (caddr_t)buf;

  if (putmsg(eph->socket, &pbuf, &dbuf, 0) < 0) {
    return(-1);
  }
  return(buflen);
}

private char ebuf[2000];	/* big enough */

export int
pi_writev(edx, iov, iovlen, eaddr)
int edx;
struct iovec *iov;
int iovlen;
unsigned char eaddr[6];
{
  int i;
  char *p;
  int len;
  
  if (edx < 1 || edx > MAXOPENPROT || eaddr == NULL)
    return(-1);
  if (!ephlist[edx-1].inuse)
    return(-1);

  for (len = 0, p = ebuf, i = 0 ; i < iovlen ; i++)
    if (iov[i].iov_base && iov[i].iov_len >= 0) {
      bcopy(iov[i].iov_base, p, iov[i].iov_len);
      p += iov[i].iov_len;	/* advance */
      len += iov[i].iov_len;	/* advance */
    }
  return(pi_write(edx, ebuf, len, eaddr));
}
SHAR_EOF
if test 8558 -ne "`wc -c < 'snitp.c'`"
then
	echo shar: "error transmitting 'snitp.c'" '(should have been 8558 characters)'
fi
chmod 440 'snitp.c'
fi
if test -f 'uab.8'
then
	echo shar: "will not over-write existing file 'uab.8'"
else
cat << \SHAR_EOF > 'uab.8'
.TH uab 8
.SH NAME
.I uab
\- Unix AppleTalk Bridge
.SH SYNTAX
.I uab
-D <level>
-f <bridge_description>
-l <log file>
-d
tdump debug nodebug statistics (stat) exit
.SH DESCRIPTION
The Unix AppleTalk
Bridge (
.I uab)
program allows certain unix systems to act as AppleTalk
Bridges.  
.I uab
can be functionally divided into two parts.  The
first is the actual AppleTalk Bridge implementation and the second are
the routines that define particular "Link Access Protocols" (aka
"hardware" delivery methods e.g. EtherTalk).  
.I uab
also supports an
internal demultiplexing that allows
packets delivered to the 
.I uab
node to be delivered to other processes
within that system.  
.PP
Currently, 
.I uab
runs on Ultrix 1.2 (and beyond) and SunOS 4.0 and
supports EtherTalk.  Unfortunately, with the current definition of
KIP's UDP encapsulation and delivery rules, it is not feasible to
implement KIP.
The only internal packet
delivery mechanism defined is a modified version of KIP's UDP
encapsulation (modified-KIP) that uses a different UDP port range over
the internal
loopback; thus CAP programs must be relinked with a different low
level delivery mechanism to work with 
.I uab.
Note that all packets for
these programs are sent and received through the 
.I uab process.
.PP
Since 
.I uab
does not understand KIP, 
it is necessary to have an AppleTalk Bridge that
understands both KIP encapsulation and EtherTalk before KIP based
"systems" (e.g. programs compiled with CAP, bridges that only speak
KIP on the ethernet interface--revisions of KIP before 2/88, etc) can
work with 
.I uab's
modified-KIP based programs.
.PP
uab is configured by use of a "bridge description" file describe in
detail later.
.PP
The flags are: D to set the debugging level to a particular value, d
to increment the debug level by one, f to specify a bridge description
file (default bridge_desc), and l to specify a logging file (default none).
.I uab
acts in response to certain signals.
.I uab
invoked as:
.nf
	uab <name>
.fi
where name is one of tdump (SIGUSR1), debug (SIGIOT), nodebug
(SIGEMT), statistics (SIGUSR2), or exit (SIGTERM) will
send these signals.
tdump causes the running uab to dump its internal tables (rtmp, etc.) to
/usr/tmp/uab.dump.  (dump is
reserved for nbp if uab ever support nbp).  debug tells the running
uab to increment the debug level: if not log file was currently
active, the log will go to /usr/tmp/uab.run.
nodebug turns off all debugging.
stat or statisitics dumps statistics to /usr/tmp/uab.stats.  exit
causes the running uab to stop.
.PP
The bridge description file is a list of the valid ports for a
particular Unix AppleTalk Bridge (UAB).
In order to minimize the maintaince headache, one may use the host
name as a selector.
Each port description is entered on a single line.
.PP
The first item in a line is the host selector field.  It can be the
host name (as returned by gethostname).  In addition, you can use % to
match any character.  "*" can be used to match any host.  Finally, you
can use "*" at the end of a string to complete a match.  (Allowing "*"
at both the beginning and end or at an arbritrary location is a pain
because it is an unanchored search -- would have to use a regular
expression matcher to do this -- ugh).
.PP
The second field contains a tuple that specifies the interface's
link access protocol (LAP) and any device specific data required.
Valid LAP method specifications:
.nf
    ELAP - EtherTalk Link Access Protocol 
    EtherTalk - same as above
.fi
The device specific data consists of a "device name" followed by an
colon and a "device number".  If the colon is omitted, the device
number is assumed to be zero.
For Ethertalk, this should be interpreted as a ethernet "tap" device
(SunOS, Ultrix).  For example, "ie:1" for ethertalk on interface ie1.
.PP
The third field specifies the local demultiplexing delivery
mechanism for delivery of DDP packets not destined for the bridge
process.  Currently defined mechanisms are: "none" which says there
will be other client processes; "mkip" - modified version of kip
style udp encapsulation using a different udp port range.
(Hopefully, there will be a way to do direct kip, asynch appletalk,
etc. in the future)
.PP
The fourth and last field specifies two items paired in a
[<item1>,<item2>] format.  The first is the DDP network that should
be associated with this port.  If you specify zero, the ddp network
number will be acquired via RTMP if there are other bridges that
know the network number.  Note that only a single network is allowed
at this point.  The network number may be specified as <number> or
<high byte>.<low byte>.  In both cases, the number may be decimal
(no leading zero), octal (leading zero), or hexadecimal (leading 0x
or 0X).  If this field is not specified,
.I uab
will assume that this port is for multiplexing only.  It will not send
out any rtmp data packets or respond to rtmp network request packets, etc.
.PP
The second item specifies the zone name associated with
the ddp network associated with this port.  If it is not specified,
it will be acquired via ZIP if there are other bridges on the
network that know the zone name.  Note: you may omit the comma if
you do not wish to specify a zone.
Note, \ can be used to quote a character (like a space in the zone
name :-)  A "\" at the end of a line continues the line.  Carriage
return and line feed both terminate a line.
.PP
You should order the file from more specific to less specific (in
terms of host name matches.  Once a match has been found, then only
matches with the exactly same pattern will succeed!
.SH BUGS
None known.
.SH NOTES
Better method for internal demuxing is needed.
.SH AUTHOR
Charlie C. Kim, Academic Computing and Communications Group, Center
for Computing Activities, Columbia University
.SH FILES
.nf
/usr/tmp/uab.stats
/usr/tmp/uab.run
/usr/tmp/uab.dump
.fi
SHAR_EOF
if test 5882 -ne "`wc -c < 'uab.8'`"
then
	echo shar: "error transmitting 'uab.8'" '(should have been 5882 characters)'
fi
chmod 440 'uab.8'
fi
if test -f 'uab.c'
then
	echo shar: "will not over-write existing file 'uab.c'"
else
cat << \SHAR_EOF > 'uab.c'
static char rcsid[] = "$Author: cck $ $Date: 88/10/10 18:21:31 $";
static char rcsident[] = "$Header: /src/local/mac/cap/etalk/RCS/uab.c,v 1.32 88/10/10 18:21:31 cck Rel $";
static char revision[] = "$Revision: 1.32 $";

/*
 * UAB - Unix AppleTalk Bridge
 *
 *  AppleTalk Bridge For Unix
 *
 *  written by Charlie C. Kim
 *     Academic Computing and Communications Group
 *     Center For Computing Activities
 *     Columbia University
 *   August 1988
 *
 *
 * Copyright (c) 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 * Permission is granted to any individual or institution to use,
 * copy, or redistribute this software so long as it is not sold for
 * profit, provided that this notice and the original copyright
 * notices are retained.  Columbia University nor the author make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Edit History:
 *
 *  April 3, 1988  CCKim Created
 *
*/

static char columbia_copyright[] = "Copyright (c) 1988 by The Trustees of \
Columbia University in the City of New York";

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/param.h>
#ifndef _TYPES
# include <sys/types.h>
#endif
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>

#include <netat/appletalk.h>
#include <netat/compat.h>
#include "mpxddp.h"
#include "gw.h"
#include "if_desc.h"
#include "log.h"

/* bridge description file */
#ifndef BRIDGE_DESC
# define BRIDGE_DESC "bridge_desc"
#endif
private char *bd_file = BRIDGE_DESC; /* default bridge description */

private int debug = 0;
private int have_log = 0;

/* import  link access protocol descriptions */
/* ethertalk */
extern struct lap_description ethertalk_lap_description;

/* IMPORT: multiplex ddp descriptions */
/* import mpx ddp module udpcap */
extern struct mpxddp_module mkip_mpx; /* modified kip udp port range */
extern struct mpxddp_module kip_mpx; /* no rebroadcaster allowed, */
				     /* rewrites atalk.local */

private void disassociate();
private int mark();		/* mark alive */
private void usage();
private char *doargs();
export void dumpether();

/* these are currently only used internally */
export boolean match_string();
export char *gettoken();
export char *getdelimitedstring();
export char *mgets();

/* guts of start */
private boolean parse_bd();
private void pbd_err();
private int parse_net();

private boolean connect_port();

/* control module */
private void setup_signals();	/* MODULE:CONTROL */
private void listsigactions();	/* MODULE:CONTROL */
private int handlesigaction();	/* MODULE:CONTROL */
private int runsignalactions();	/* MODULE:CONTROL */
private void set_uab_pid();	/* MODULE:CONTROL */
private int get_uab_pid();	/* MODULE:CONTROL */

/*
 * give away terminal
 *
*/
private void
disassociate()
{
  int i;
  /* disassociate */
  if (fork())
    _exit(0);			/* kill parent */
  for (i=0; i < 3; i++) close(i); /* kill */
  (void)open("/",0);
#ifdef NODUP2
  (void)dup(0);			/* slot 1 */
  (void)dup(0);			/* slot 2 */
#else
  (void)dup2(0,1);
  (void)dup2(0,2);
#endif
#ifdef TIOCNOTTY
  if ((i = open("/dev/tty",2)) > 0) {
    (void)ioctl(i, TIOCNOTTY, (caddr_t)0);
    (void)close(i);
  }
#endif
}

private void
usage()
{
  fprintf(stderr,"Usage: uab -D <debug level> -f <bridge description file>\n");
  listsigactions();
  exit(1);
}

private char *
doargs(argc, argv)
int argc;
char **argv;
{
  int c, pid;
  extern char *optarg;
  extern int optind;

  while ((c = getopt(argc, argv, "D:df:l:")) != EOF) {
    switch (c) {
    case 'f':
      bd_file = optarg;
      break;
    case 'D':
      debug = atoi(optarg);
      break;
    case 'd':
      debug++;
      break;
    case 'l':
      have_log = 1;
      logfileis(optarg, "w");
      break;
    }
  }

  if (optind == argc)
    return;
  
  if ((pid = get_uab_pid()) < 0) 
    log(L_EXIT|LOG_LOG,
	"Couldn't get Unix AppleTalk Bridge pid, is it running?");

  for (; optind < argc ; optind++)
    if (handlesigaction(argv[optind], pid) < 0)
      usage();
  exit(0);
}

main(argc, argv)
char **argv;
int argc;
{
  int mark();

  doargs(argc, argv);
  set_debug_level(debug);
  if (!debug) {
    disassociate();
    if (!have_log)
      nologfile();		/* clear stderr as log file */
  }
  setup_signals();
  ddp_route_init();		/* initialize */
  if (!parse_bd(bd_file))
    exit(1);

  ddp_route_start();
  set_uab_pid();		/* remember */
  /* mark every 30 minutes */
  Timeout(mark, (caddr_t)0, sectotick(60*30));
  do {
    abSleep(sectotick(30), TRUE);
  } while (runsignalactions());
}

private int
mark()
{
  log(LOG_LOG, "uab running");
}

export void
dumpether(lvl,msg, ea)
int lvl;
char *msg;
byte *ea;
{
  log(lvl, "%s: %x %x %x %x %x %x",msg,
	 ea[0], ea[1], ea[2],
	 ea[3], ea[4], ea[5]);
}

/*
 * start of guts
 *   parse the file
 *
*/

/* list of known allowable lap descriptions */
private struct lap_description *ld_list[] = {
  &ethertalk_lap_description,
  NULL
};

/* list of valid interfaces */
private struct interface_description *id_list;

private struct mpxddp_module *mdm_list[] = {
  &kip_mpx,
  &mkip_mpx,
  NULL
};

private struct mpxddp_module *check_local_delivery();
private struct lap_description *check_lap_type();

/*
 * check out local delivery methods (couldn't get the pointers right,
 * so just hard coded the names :-)
 *
*/
private struct mpxddp_module *
check_local_delivery(local, invalid)
char *local;
int *invalid;
{
  struct mpxddp_module **m;

  *invalid = FALSE;
  if (strcmpci(local, "none") == 0)
    return(NULL);
  
  for (m = mdm_list; *m ; m++) {
    if (strcmpci((*m)->mpx_key, local) == 0)
      return(*m);
  }
  *invalid = TRUE;
  return(NULL);
}

/*
 * check for a valid lap type
 *
*/
private struct lap_description *
check_lap_type(name)
char *name;
{
  struct lap_description **ld;
  char **keys;

  for (ld = ld_list; *ld ; ld++) {
    for (keys = (*ld)->ld_key; *keys; keys++)
      if (strcmpci(*keys, name) == 0)
	return(*ld);
  }
  return(NULL);
}

private void
interface_dump_table(fd)
FILE *fd;
{
  IDESC_TYPE *id = id_list;

  while (id) {
    if (id->id_ld && id->id_ld->ld_dump_routine)
      (*id->id_ld->ld_dump_routine)(fd, id);
    id = id->id_next;		/* move to next in our list */
  }
}

private void
interface_dump_stats(fd)
FILE *fd;
{
  IDESC_TYPE *id = id_list;

  while (id) {
    if (id->id_ld && id->id_ld->ld_dump_routine)
      (*id->id_ld->ld_stats_routine)(fd, id);
    id = id->id_next;		/* move to next in our list */
  }
}

/*
 * match base against pattern
 * special chars are
 * allow: % to match any char
 *        * (at start) to match anything (anything following * is ignored)
 *        * at end to match previous then anything
 *
*/
export boolean
match_string(base, pattern)
char *base;
char *pattern;
{
  char pc, bc;

  while ((pc = *pattern++)) {
    if ((bc = *base) == '\0')
      return(FALSE);
    switch (pc) {
    case '%':
      break;
    case '*':
      return(TRUE);
    default:
      if (bc != pc)
	return(FALSE);
      break;
    }
    base++;
  }
  if (*base != '\0')		/* end of string */
    return(FALSE);		/* no, and pattern has ended! */
  return(TRUE);
}

/*
 * get a token.
 *  returns a pointer to a copy of the token (must be saved if you
 *  wish to keep across invocations of gettoken).
 *
 * We depend upon isspace to define white space (should be space, tab,
 * carriage return, line feed and form feed
 *
 * "/" may be used to quote the characters.
 *
 * "#" at the start of a line (possibly with white space in front)
 * is consider a comment.
 *
*/
private char tmpbuf[BUFSIZ];	/* temp for gettoken and getdel...string */
#define TMPBUFENDP (tmpbuf+BUFSIZ-2) /* room for null */
char *
gettoken(pp)
char **pp;
{
  char *p = *pp;
  char *dp = tmpbuf;
  char c;
  boolean sawquote;

  /* no string or at the end */
  if (p == NULL || (c = *p) == '\0')
    return(NULL);

  while ((c = *p) && isascii(c) && isspace(c)) /* skip over any spaces */
    p++;

  if (*p == '#') {		/* is a comment */
    *pp = p;			/* repoint */
    return(NULL);
  }

  for (sawquote=FALSE, c = *p ; c != '\0' && dp < TMPBUFENDP; c = *p) {
    if (sawquote) {		/* in quote mode? */
      *dp++ = c;		/* yes, move char in */
      sawquote = FALSE;		/* and turn off quote mode */
    } else if (c == '\\')	/* else, is the char a quote? */
      sawquote = TRUE;		/* yes, turn quote flag on */
    else if (isascii(c) && isspace(c)) /* or is it a space? */
      break;			/* yes, so stop tokenizing */
    else *dp++ = c;		/* wasn't in quote, wasn't a quote */
				/* char and wasn't a space, so part of */
				/* token */
    p++;			/* move past char */
  }
  *dp = '\0';			/* tie off string */
  *pp = p;			/* update pointer */
  if (tmpbuf == dp)		/* nothing in string? */
    return(NULL);
  return(tmpbuf);			/* return pointer to token */
}

/*
 * get a string deliminted by the characters start and end
 *
*/
export char *
getdelimited_string(pp, start, end)
char **pp;
char start;
char end;
{
  char *p = *pp;
  char *dp = tmpbuf;
  char c;
  boolean sawquote;

  if (start)
    while ((c = *p) && c != start) /* skip to start */
      p++;
  *pp = p;
  if (c == '\0')
    return(NULL);
  p++;				/* skip begin char */
  for (sawquote=FALSE,c = *p; c != '\0' || dp < TMPBUFENDP; c = *p) {
    if (sawquote) {		/* in quote mode? */
      *dp++ = c;		/* yes, move char in */
      sawquote = FALSE;		/* and turn off quote mode */
    } else if (c == '\\')	/* else, is the char a quote? */
      sawquote = TRUE;		/* yes, turn quote flag on */
    else if (c == end) {	/* or is it our end char */
      p++;			/* yes, push past char */
      break;			/* and stop tokenizing */
    }
    else *dp++ = c;		/* wasn't in quote, wasn't a quote */
				/* char and wasn't a end char, so part */
				/* of token */
    p++;			/* move past char */
  }
  *dp = '\0';			/* tie off string */
  *pp = p;			/* update pointer */
  if (tmpbuf == dp)		/* nothing in string? */
    return(NULL);
  return(tmpbuf);			/* return pointer to token */
}


/*
 * like gets, but accepts fd, will always null terminate,
 * accepts "\" at the end of a line as a line continuation.
 *
*/
export char *
mgets(buf, size, fd)
char *buf;
int size;
FILE *fd;
{
  int c;
  char lc = 0;
  char *p = buf;

  if (size)			/* make room for the null */
    size--;
  while ((c = getc(fd)) != EOF && size) {
    if (c == '\n' || c == '\r') {
      if (lc == '\\') {
	p--;			/* backup pointer (toss the "\" */
	lc = 0;			/* and clear lc */
	continue;
      }
      break;
    }
    *p++ = c;
    size--;
    lc = c;
  }
  *p = '\0';			/* tie off string */
  return(c == EOF ? NULL : buf);
}


/*
 * parse a bridge description file
 *
*/
private boolean
parse_bd(file)
char *file;
{
  FILE *fd;
  char hostname[BUFSIZ];	/* room for host name */
  char buf[BUFSIZ];		/* room for a lot */
  char *p, *bp, *t;
  char *savedpat = NULL;
  int i;
  int lineno = 0;		/* start line numbers */
  int connected_ports = 0;
  int anymatches = TRUE;
  LDESC_TYPE *ld;
  IDESC_TYPE *id;
  
  if ((id = (IDESC_TYPE *)malloc(sizeof(IDESC_TYPE))) == NULL)
    log(L_EXIT|L_UERR|LOG_LOG, "out of memory in parse_pd");

  if (gethostname(hostname, sizeof(hostname)) < 0) {
    log(L_UERR|LOG_LOG, "Can't get hostname: will only accept wildcards");
    hostname[0] = '\0';
  }

  if ((fd = fopen(file, "r")) == NULL)
    log(L_EXIT|L_UERR|LOG_LOG, "Can't open file %s", file);

  while (mgets(buf, sizeof(buf),fd)) {
    lineno++;
    p = buf;
    /* get the tokens */
    if ((bp = gettoken(&p)) == NULL) /* comment or blank */
      continue;
    if (savedpat && strcmp(bp, savedpat) == 0);
    if (!savedpat) {
      /* no saved pattern */
      if (!match_string(hostname, bp)) /* none of our concern */
	continue;
      savedpat = (char *)strdup(bp);	/* save the pattern */
    } else {
      /* saved pattern */
      if (savedpat && strcmp(bp, savedpat) != 0)
	continue;
    }
    log(LOG_PRIMARY, "Port description match on %s", bp);
    anymatches = TRUE;
    if ((bp = getdelimited_string(&p, '[', ']')) == NULL) {
      pbd_err(lineno, buf, "missing lap specification");
      continue;
    }
    if ((t = (char *)index(bp, ','))) {
      *t = '\0';		/* divide */
      t++;			/* and conquer*/
    }
    if ((ld = check_lap_type(bp)) == NULL) {
      pbd_err(lineno,buf, "invalid lap type");
      continue;
    }
    id->id_ld = ld;		/* remember for later */
    id->id_intf = NULL;
    id->id_intfno = 0;
    bp = t;			/* move to device dependent */
    if (bp) {
      if ((t = (char *)index(bp, ':'))) {
	id->id_intfno = atoi(t+1);	/* convert to int */
	if (id->id_intfno == 0 && t[1] != '0') {
	  pbd_err(lineno,buf,
	      "interface specification, expected number after colon");
	  continue;
	}
	*t = '\0';		/* kill of interface # */
      }
      id->id_intf = (char *)strdup(bp); /* copy interface name */
    } else {
      if (ld->ld_wants_data) {
	pbd_err(lineno, buf, "this lap type requires additional data");
	continue;
      }
    }
    if ((bp = gettoken(&p)) == NULL) {
      pbd_err(lineno, buf, "missing local delivery");
      if (id->id_intf)
	free(id->id_intf);
      continue;
    }
    id->id_local = check_local_delivery(bp, &i);
    if (i) {
      pbd_err(lineno, buf, "invalid local delivery");
      if (id->id_intf)
	free(id->id_intf);
      continue;
    }
    id->id_zone = NULL;
    if ((bp = getdelimited_string(&p, '[', ']')) != NULL) {
      byte *z;

      id->id_isabridge = TRUE;
      id->id_network = parse_net(bp, NULL);	/* get network number */
      z = (byte *)index(bp, ',');
      if (z) {
	z = (byte *)strdup(z);	/* duplicate string */
	*z = strlen(z+1);	/* make pstr */
	id->id_zone = z;
      } else id->id_zone = NULL;
      id->id_zone = z ? ((byte *)strdup(z)) : NULL;
    } else id->id_isabridge = FALSE;
    log(LOG_PRIMARY, "interface %s%d", id->id_intf, id->id_intfno);
    if (id->id_local)
      log(LOG_PRIMARY, "\tlocal delivery is %s", id->id_local->mpx_name);
    else
      log(LOG_PRIMARY, "\tno local delivery");
    if (id->id_isabridge) {
      if (id->id_network)
	log(LOG_PRIMARY, "\tnetwork %d.%d", nkipnetnumber(id->id_network),
	       nkipsubnetnumber(id->id_network));
      else
	log(LOG_PRIMARY, "\tnetwork number from network");
      if (id->id_zone)
	log(LOG_PRIMARY, "\tzone %d-'%s'", *id->id_zone, id->id_zone+1);
    } else
      log(LOG_PRIMARY, "\tnot routing on this interface");
    if ((*ld->ld_init_routine)(id, TRUE)) {
      connected_ports++;
      id->id_next = id_list;	/* link into list */
      id_list = id;		/* of active ld descriptions */
      /* create a new id */
      if ((id = (IDESC_TYPE *)malloc(sizeof(IDESC_TYPE))) == NULL)
	log(L_EXIT|L_UERR|LOG_LOG, "out of memory in parse_pd");
    } else {
      log(LOG_PRIMARY, "Couldn't establish the port");
    }
    if (id->id_intf)
      free(id->id_intf);
    if (id->id_zone)
      free(id->id_zone);
  }
  if (id)
    free(id);
  if (savedpat)
    free(savedpat);
  fclose(fd);
  if (connected_ports == 0) {
    log(LOG_PRIMARY, "NO CONNECTED PORTS, ABORTING");
    return(FALSE);
  }
  return(TRUE);
}

private void
pbd_err(lineno, line, msg)
{
  log(LOG_LOG, "error in line %d - %s",lineno, msg);
  log(LOG_LOG, "line: %s", line);
}

/*
 * parse a network number in the format:
 *  number
 *  number.number (means high byte, low byte)
 * where the number can be hex (0x or 0X preceeding) or octal (leading 0) 
 *  (handled by strtol)
 *
 * set *rptr to point to last part of "s" used
 *
*/
private int
parse_net(s, rptr)
char *s;
char **rptr;
{
  char *e;
  int p1, p2;
  
  p1 = strtol(s, &e, 0);	/* convert */
  if (rptr)
    *rptr = e;
  if (*e != '.')
    return(htons(p1));
  p2 = strtol(e+1, rptr, 0);
  return(htons( ((p1&0xff) << 8) | (p2&0xff)));
}


/* MODULE: CONTROL */

/* pid file */
#ifndef UAB_PIDFILE
# define UAB_PIDFILE "/etc/uab.pid"
#endif

/* logging file */
#ifndef UAB_RUNFILE
# define UAB_RUNFILE "/usr/tmp/uab.run"
#endif

#ifndef UAB_STATSFILE
# define UAB_STATSFILE "/usr/tmp/uab.stats"
#endif

#ifndef UAB_DUMPFILE
# define UAB_DUMPFILE "/usr/tmp/uab.dump"
#endif

private char *uab_runfile = NULL;
private char *uab_pidfile = UAB_PIDFILE;
private char *uab_statsfile = UAB_STATSFILE;
private char *uab_dumpfile = UAB_DUMPFILE;

private int uab_end();
private int uab_undebug();
private int uab_debuginc();
private int table_dump();
private int stats_dump();

/*
 * do nice stuff
 *
*/

private void
set_uab_pid()
{
  FILE *fd;

  if ((fd = fopen(uab_pidfile, "w")) != NULL) {
    fprintf(fd, "%d\n",getpid());
    fclose(fd);
  }
}

private
get_uab_pid()
{
  FILE *fp;
  int pid;

  if ((fp = fopen(uab_pidfile, "r")) == NULL) {
    log(L_UERR|LOG_LOG, "No pid file - maybe the daemon wasn't running?");
    return(-1);
  }
  if (fscanf(fp, "%d\n", &pid) != 1) {
    log(LOG_LOG, "pid file was bad");
    return(-1);
  }
  return(pid);
}


#define NSIGACT 6

struct sigtab {
  char *s_name;
  int s_signal;
  char *s_action;
};

private struct sigtab sigtab[NSIGACT] = {
#define SIG_TDUMP SIGUSR1
  "tdump", SIG_TDUMP, "dump tables",
#define SIG_DEBUG SIGIOT
  "debug", SIG_DEBUG, "increment debug level",
#define SIG_NODEBUG SIGEMT
  "nodebug", SIG_NODEBUG, "clear debugging",
#define SIG_STAT SIGUSR2
  "statistics", SIG_STAT, "dump statistics",
  "stat", SIG_STAT, "dump statistics",
#define SIG_EXIT SIGTERM
  "exit", SIG_EXIT, "stop running uab"
};

private int signalactions; /* no signal actions to handle */
#define SA_TABLE_DUMP 0x1
#define SA_STATISTICS 0x2
#define SA_UNDEBUG 0x4		/* turn off debugging */
#define SA_DEBUG 0x8		/* turn on debugging */
private void
setup_signals()
{
  signal(SIG_STAT, stats_dump);
  signal(SIG_TDUMP, table_dump);
  signal(SIG_EXIT, uab_end);
  signal(SIG_DEBUG, uab_debuginc);
  signal(SIG_NODEBUG, uab_undebug);
  signalactions = 0; /* no signal actions to handle */
}

private void
listsigactions()
{
  int i;
  struct sigtab *st;

  fprintf(stderr, " commands:\n");
  for (st = sigtab, i = 0; i < NSIGACT; i++, st++)
    fprintf(stderr,"\t%s\t%s\n", st->s_name, st->s_action);
}

private int
handlesigaction(s, pid)
char *s;
{
  int i;
  struct sigtab *st;

  for (st = sigtab, i = 0; i < NSIGACT; i++, st++) {
    if (strcmpci(s, st->s_name) != 0)
      continue;
    if (kill(pid, st->s_signal) < 0)
      log(LOG_LOG, "Couldn't send %s signal to daemon[%d] - is it running?",
	  st->s_action, pid);
    else
      log(LOG_LOG, "Sent %s signal to daemon[%d]",st->s_action,pid);
    return(0);
  }
  return(-1);
}


/* safe enough */
private int
uab_end()
{
  log(LOG_LOG, "Exiting - stopped by remote");
  unlink(uab_pidfile);
  exit(0);
}

/* nothing yet */
/* unsafe (defer until later) */
private int
table_dump()
{
  signalactions |= SA_TABLE_DUMP;
  signal(SIG_TDUMP, table_dump);
}

private int
stats_dump()
{
  signalactions |= SA_STATISTICS;
  signal(SIG_STAT, stats_dump);
}

private int
uab_undebug()
{
  debug = 0;
  signalactions |= SA_UNDEBUG;
  signal(SIG_NODEBUG, uab_undebug);
}

private int
uab_debuginc()
{
  signalactions |= SA_DEBUG;
  debug++;
  signal(SIG_DEBUG, uab_debuginc);
}

/*
 * run deferred actions
 *
 * returns FALSE if everythings should stop
*/
private int
runsignalactions()
{
  int done = 0;			/* init to zero */

  while (signalactions) {
    /* always allow undebug and debug to run */
    if ((SA_UNDEBUG & signalactions) && debug == 0) {
      set_debug_level(debug);
      log(LOG_LOG, "DEBUGGING OFF");
      if (uab_runfile) {
	nologfile();
	uab_runfile = NULL;		/* reset this */
      }
      signalactions &= ~SA_UNDEBUG;
    }
    if ((SA_DEBUG & signalactions) && debug != 0) {
      if (!islogfile()) {
	uab_runfile = UAB_RUNFILE;
	logfileis(uab_runfile, "w+");
      }
      set_debug_level(debug);
      log(LOG_LOG, "Debugging level is now %d", debug);
      signalactions &= ~SA_DEBUG;
    }
    /* only run one of these at a time */
    if (SA_TABLE_DUMP & signalactions) {
      FILE *fd;
      long t;

      done = SA_TABLE_DUMP;
      if ((fd = fopen(uab_dumpfile, "a+")) == NULL) {
	log(LOG_LOG|L_UERR, "can't open dump file %s", uab_dumpfile);
      } else {
	chmod(uab_dumpfile, 0774);
	time(&t);
	fprintf(fd, "Tables dump at %s", ctime(&t));
	interface_dump_table(fd);
	rtmp_dump_table(fd);
	fclose(fd);
      }
    } else if (SA_STATISTICS & signalactions) {
      FILE *fd;
      long t;
      
      done = SA_STATISTICS;
      if ((fd = fopen(uab_statsfile, "a+")) == NULL) {
	log(LOG_LOG|L_UERR, "can't open statistics file %s", uab_statsfile);
      } else {
	chmod(uab_statsfile, 0774);
	time(&t);
	fprintf(fd, "Statistics dump at %s", ctime(&t));
	ddp_dump_stats(fd);
	rtmp_dump_stats(fd);
	interface_dump_stats(fd);
	fclose(fd);
      }
    }
    signalactions &= ~done;	/* turn off run signals */
  }
  return(TRUE);
}

SHAR_EOF
if test 20900 -ne "`wc -c < 'uab.c'`"
then
	echo shar: "error transmitting 'uab.c'" '(should have been 20900 characters)'
fi
chmod 440 'uab.c'
fi
if test -f 'abmkip.c'
then
	echo shar: "will not over-write existing file 'abmkip.c'"
else
cat << \SHAR_EOF > 'abmkip.c'
/*
 * $Author: cck $ $Date: 88/08/28 04:40:54 $
 * $Header: abkip.c,v 1.2 88/08/28 04:40:54 cck Exp $
 * $Revision: 1.2 $
*/

/*
 * abkip.c - KIP (UDP encapsulated DDP packets) network module
 *
 *  abkip provides the interface from DDP to the outside world as it
 *  sees it.  It includes the DDP module "routeddp".
 *
 *  NOTE: when running KIP, no other LAP protocols are allowed
 * 
 *  The justification for including parts of "lap", "ddp", etc. is that
 *  that we are "gatewaying" and are allowed to do funny things at the
 *  boundaries
 *
 * AppleTalk package for UNIX (4.2 BSD).
 *
 * Copyright (c) 1986, 1987, 1988 by The Trustees of Columbia University 
 *  in the City of New York.
 *
 *
 * Edit History:
 *
 *  June 14, 1986    Schilit	Created.
 *  June 18, 1986    CCKim      Chuck's handler runs protocol
 *
 */
/*
 * The following list of exported routines is provided so you'll know what
 * have to be done to do another interface type (ethertalk, etc)
 *
 * EXPORTED ROUTINES:
 *
 *  OSErr GetNodeAddress(int *mynode, int *mynet)
 *     Return node addresses
 *  int abInit(boolean dispay_message)
 *     Initialize AppleTalk
 *  int abOpen(int *returnsocket, int wantsocket, struct iovec iov[], iovlen)
 *     Open a DDP socket
 *  int abClose(int socket)
 *     Close a DDP socket
 *  void abnet_cacheit(word srcNet, byte srcNode)
 *     Call in DDP protocol layer to tell the lower layer that
 *      the last packet that came in was from srcNet, srcNode
 *  int routeddp(struct iovec *iov, int iovlen)
 *     This is the DDP incursion.  With a full AppleTalk implementation,
 *     this would be part of DDP (abddp2).  This routes the DDP packet:
 *     normally would decide where to send and then send via lap, with KIP
 *     decides where and sends via UDP.
 *
*/

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <netdb.h>
#include <netat/appletalk.h>
#include <netat/compat.h>	/* to cover difference between bsd systems */

/*
 * Configuration defines
 *
 * NORECVMSG - no recvmsg
 * NOSENDMSG - no sendmsg
 * NEEDMSGHDR - no msghdr in sockets.h - define our own
 *
*/
#ifdef NORECVMSG
# ifndef NEEDNETBUF
#  define NEEDNETBUF
# endif
#endif
#ifdef NOSENDMSG
# ifndef NEEDNETBUF
#  define NEEDNETBUF
# endif
#endif

/* for forwarding */
/* These three define a lookaside */
/* could possibly be an array */
/* cf. ip_resolve */
private struct in_addr ipaddr_src; /* ip address */
/* ddp address */
private word ddp_srcnet;	/* ddp network part */
private byte ddp_srcnode;	/* ddp node part */

import struct in_addr bridge_addr; /* .. nearest known bridge */

private struct sockaddr_in from_sin; /* network struct of last packet rec. */

#define rebPort 902		/* 0x386 */
word rebport;			/* used to hold swabbed rebPort */

private struct sockaddr_in abfsin; /* apple bus foreign socketaddr/internet */
private int abfd;		/* apple bus socket */

import word this_net;		/* our network number */
import byte this_node;		/* our node number */
import word nis_net;		/* network number of our nis */
import byte nis_node;		/* node number of our nis */

import int ddp_protocol();	/* DDP protocol handler */
private int kip_get();
export DBUG dbug;		/* debug flags */

/* BUG: bind doesn't work when lsin is on the stack! */
private struct sockaddr_in lsin; /* local socketaddr/internet */
private int skt2fd[ddpMaxSkt+1]; /* translate socket to file descriptor */


private LAP laph;

/* don't really need this */
#ifdef notdef
private char lapdata[lapMaxData];
private struct iovec lapiov[IOV_LAP_SIZE+1]; /* lap header + data */
#endif

/*
 * OSErr GetNodeAddress(int *myNode,*myNet)
 *
 * GetNodeAddress returns the net and node numbers for the current
 * host.
 *
 * N.B. - the myNet address is in net (htons) format.
 *
*/
export OSErr
GetNodeAddress(myNode,myNet)
int *myNode,*myNet;
{
  *myNode = this_node;
  *myNet = this_net;
  return(noErr);			   /* is ok */
}
struct in_addr xdesthost;
/*
 * initialize
 *
*/
export
abInit(disp)
{
  int i;
  extern word bridge_net;
  extern byte bridge_node;

  for (i=0; i < ddpMaxSkt+1; i++) {
    skt2fd[i] = -1;		/* mark all these as unused */
  }

  rebport = htons(rebPort);	/* swap to netorder */
  init_fdlistening();

  /* no need to bind since we don't recv on this socket, just send... */
  if ((abfd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
    perror("abinit");
    return(abfd);
  }
  abfsin.sin_family = AF_INET;
  abfsin.sin_addr.s_addr = INADDR_ANY;

  bridge_node = 0, bridge_net = 0;
  openatalkdb("/usr/local/lib/cap/etalk.local"); /* sets up this_* */
  if (disp) {
      printf("abInit: [ddp: %3d.%02d, %d]",
	     ntohs(this_net)>>8, htons(this_net)&0xff, this_node);
      if (this_net != nis_net || this_node != nis_node)
	printf(", [NBP (atis) Server: %3d.%02d, %d]",
	       ntohs(nis_net)>>8, htons(nis_net)&0xff, nis_node);
      printf(" starting\n");
  }
  xdesthost.s_addr = inet_addr("127.1");
  if (xdesthost.s_addr == -1)
    return(-1);
  DDPInit();
#ifdef notdef
  /* nonsensical */
  LAPOpenProtocol(lapDDP,ddp_protocol); /* open protocol */
#endif

  return(0);
}  

/*
 * int abOpen(int *skt,rskt, iov, iovlen)
 *
 * abOpen opens the ddp socket in "skt" or if "skt" is zero allocates
 * and opens a new socket.  Upon return "skt" contains the socket number
 * and the returned value is >=0 if no error, < 0 if error.
 *
 * iov should be an array of type "struct iov" of length at least
 * IOV_LAP_SIZE+1.  Levels after IOV_LAP_LVL are assume to filled.
 *
*/
int abOpen(skt,rskt, iov, iovlen)
int *skt;
int rskt;
struct iovec *iov;
int iovlen;
{
  int i,fd,err;
  word ipskt;

#ifdef notdef
    /* could check iov not null and set to lapiov if not, etc */
    /* but necessary since only (likely) client is ddp */
    /* note: story is different for ethertalk, etc */
#endif
  /* good enough for now */
  if (iov == NULL || iovlen < IOV_LAP_SIZE+1 || iovlen > IOV_READ_MAX)
    return(-1);

  if ((fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
    perror("abopen");
    return(fd);
  }
  lsin.sin_family = AF_INET;
  lsin.sin_addr.s_addr = INADDR_ANY;

  *skt = (rskt == 0 ? ddpWKS : rskt); /* zero rskt is free choice */
/*  ipskt = ddp2ipskt(*skt);	/* translate into ip socket number */
  ipskt = *skt & 0x80 ? *skt + ddpNWKSUnix : *skt + 200;
  for (i=0; i < 128; i++,ipskt++,(*skt)++) {
    lsin.sin_port = htons(ipskt);
    if ((err = bind(fd, (caddr_t)&lsin, sizeof(lsin))) == 0)
      break;
    if (rskt != 0)		/* bind failed and wanted exact? */
      return(err);		/* yes... */
  }
  if (err == 0 && i < 128) {
    iov[IOV_LAP_LVL].iov_base = (caddr_t)&laph; /* remember this */
    iov[IOV_LAP_LVL].iov_len = lapSize; /* and this */
    fdlistener(fd, kip_get, iov, iovlen); /* remember for later */
    skt2fd[*skt] = fd;		/* remember file descriptor for socket */
    return(noErr);
  }
  perror("abopen bind");
  close(fd);
  return(err);
}

/*
 * close off socket opened by abClose
 *
*/
export int
abClose(skt)
int skt;
{
  int fd;

  if (skt < 0 || skt > ddpMaxSkt) {
    fprintf(stderr,"abClose: skt out of range\n");
    exit(0);
  }
  fd = skt2fd[skt];
  if (fd < 0)
    return(0);
  if (close(fd) != 0)
    perror("abClose");		/* some error... */
  fdunlisten(fd);
  skt2fd[skt] = -1;		/* mark as unused */
  return(0);
}

#ifdef NEEDNETBUF
#ifdef NEEDMSGHDR
struct msghdr {
  caddr_t msg_name;		/* name to send to */
  int msg_namelen;		/* size of name */
  struct iovec *msg_iov;	/* io vec */
  int msg_iovlen;		/* length */
  int msg_accrights;		/* dummy */
  int msg_accrightslen;
};
#endif

/* buffer larger than maximum ddp pkt by far */
private char net_buffer[ddpMaxData*2];

#ifdef NOSENDMSG
/*
 * limited sendmsg - limits to sizeof(net_buffer)
 *
*/
sendmsg(fd, msg, flags)
int fd;
struct msghdr *msg;
int flags;
{
  int err;
  int i, pos, len;
  struct iovec *iov;

  iov = msg->msg_iov;
  for (i=0, pos=0; i < msg->msg_iovlen; i++, iov++) {
    len = iov->iov_len;
    if (len+pos > sizeof(net_buffer)) /* if overflow */
      len = sizeof(net_buffer)-pos; /* then limit */
    bcopy(iov->iov_base, net_buffer+pos, len);
    pos+= len;
    if (len != iov->iov_len)	/* we don't have any more space */
      break;
  }
  len = pos;
  if ((err=sendto(abfd,net_buffer,len,0,msg->msg_name,msg->msg_namelen)) < 0)
    perror("abwrite");
  return(err);
}

#endif /* NO SENDMSG */

#ifdef NORECVMSG
recvmsg(fd, msg, flags)
int fd;
struct msghdr *msg;
int flags;
{
  int err;
  int i, pos, len, blen;
  struct iovec *iov;

  err = recvfrom(fd, net_buffer, sizeof(net_buffer), 0,
		 msg->msg_name, &msg->msg_namelen);
  if (err < 0)
    perror("abread");
  for (blen=err,pos=0,i=0,iov=msg->msg_iov; i < msg->msg_iovlen; i++, iov++) {
    len = min(iov->iov_len, blen);
    if ((pos + len) > sizeof(net_buffer)) /* if asking for too much */
      len = sizeof(net_buffer) - pos; /* then limit */
    bcopy(net_buffer+pos, iov->iov_base, len);
    pos += len;
    blen -= len;
    /* either no more room or no more data */
    if (len != iov->iov_len)
      break;
  }
  return(err);  
}
#endif /* NO RECVMSG */
#endif

#ifdef notdef
abwrite(addr, skt, iov,iovlen)
struct in_addr addr;
unsigned short skt;
struct iovec *iov;
{
  struct msghdr msg;
  int err;

  abfsin.sin_addr = addr;
  abfsin.sin_port = skt;
  msg.msg_name = (caddr_t) &abfsin;
  msg.msg_namelen = sizeof(abfsin);
  msg.msg_iov = iov;
  msg.msg_iovlen = iovlen;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((err = sendmsg(abfd,&msg,0)) < 0)
    perror("abwrite");
  return(err);  
}
abread(fd, iov, iovlen)
struct iovec *iov;
{

  msg.msg_name = (caddr_t) &from_sin;
  msg.msg_namelen = sizeof(from_sin);
  msg.msg_iov = iov;
  msg.msg_iovlen = iovlen;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((err = recvmsg(fd,&msg,0)) < 0)
    perror("abread");
  return(err);  
}

#endif

private int
kip_get(fd, iov, iovlen)
int fd;
struct iovec *iov;
int iovlen;
{
  struct msghdr msg;
  int len;
  LAP *lap;

  msg.msg_name = (caddr_t) &from_sin;
  msg.msg_namelen = sizeof(from_sin);
  msg.msg_iov = iov;
  msg.msg_iovlen = iovlen;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((len = recvmsg(fd,&msg,0)) < 0) {
    perror("abread");
    return(len);
  }
  if (iov->iov_len != lapSize) /* check */
    return(-1);
  lap = (LAP *)iov->iov_base;
  switch (lap->type) {
  case lapDDP:
    return(ddp_protocol(iov+1, iovlen-1, len-lapSize));
    break;
  default:
    return(-1);
  }
  return(-1);
}

/*
 * This is the DDP/UDP interface 
 *
*/

/* srcNet and node of last incoming packet sent to DDP */
/* and valid */
export void
abnet_cacheit(srcNet, srcNode)
word srcNet;
byte srcNode;
{
  ddp_srcnet = srcNet;		/* remember where last packet came from */
  ddp_srcnode = srcNode;
  ipaddr_src.s_addr = (from_sin.sin_port == rebport) ? 0 :
    from_sin.sin_addr.s_addr;
}

private int
ip_resolve(ddpnet, ddpnode, iphost)
word ddpnet;
byte ddpnode;
struct in_addr *iphost;
{
  if (ipaddr_src.s_addr != 0 && ddpnet == ddp_srcnet && ddpnode == ddp_srcnode)
    iphost->s_addr = ipaddr_src.s_addr;
  else
    iphost->s_addr = bridge_addr.s_addr;
}

private LAP lap;

export int
routeddp(iov, iovlen)
struct iovec *iov;
int iovlen;
{
  struct msghdr msg;
  word destskt;
  struct in_addr desthost;
  DDP *ddp;
  int err;

  ddp = (DDP *)iov[IOV_DDP_LVL].iov_base; /* pick out ddp header */

  /* check ddp socket(s) for validity */
  if ( ddp->srcSkt == 0 || ddp->srcSkt == ddpMaxSkt ||
       ddp->dstSkt == 0 || ddp->dstSkt == ddpMaxSkt ||
      skt2fd[ddp->srcSkt] == -1 )
    return(ddpSktErr);

  /* KIP routing code */
  /* establish dest socket */
/*   destskt = (word)htons(ddp2ipskt(ddp->dstSkt)); */
  destskt = (word)htons(ddp->dstSkt & 0x80 ? ddp->dstSkt + ddpNWKSUnix :
			ddp->dstSkt + 200);
  /* resolve mapping */
  ip_resolve(ddp->dstNet, ddp->dstNode, &desthost);

  /* establish a dummy lap header */
  lap.type = lapDDP;
  lap.dst = ddp->dstNode;
  lap.src = this_node;
  iov[IOV_LAP_LVL].iov_base = (caddr_t) &lap; /* LAP header */
  iov[IOV_LAP_LVL].iov_len = lapSize; 	  /* size  */

  /* send through */
  abfsin.sin_addr = xdesthost;
  abfsin.sin_port = htons(903);
  msg.msg_name = (caddr_t) &abfsin;
  msg.msg_namelen = sizeof(abfsin);
  msg.msg_iov = iov;
  msg.msg_iovlen = iovlen;
  msg.msg_accrights = 0;
  msg.msg_accrightslen = 0;
  if ((err = sendmsg(abfd,&msg,0)) < 0)
    perror("abwrite");
  return(err);  
}
SHAR_EOF
if test 12483 -ne "`wc -c < 'abmkip.c'`"
then
	echo shar: "error transmitting 'abmkip.c'" '(should have been 12483 characters)'
fi
chmod 440 'abmkip.c'
fi
if test -f 'whatiswhat'
then
	echo shar: "will not over-write existing file 'whatiswhat'"
else
cat << \SHAR_EOF > 'whatiswhat'
Unix AppleTalk Bridge
	gw.h		gateway definitions
	uab.c		gateway driver
	bridge_desc	bridge descriptions
	desc.ms		general description

DDP/RTMP/ZIP
	ddprouter.c	ddp router
	ddpsvcs.c	ddp services
	rtmp.c		rtmp routines

DDP PORT management
	ddpport.c	ddp port management
	ddpport.h	ddp port definitions
	node.h		lap node description
	if_desc.h	(lap) interface descriptions

EtherTalk (ELAP)
	aarp.c		aarp routines
	aarp_defs.h	aarp specific header
	aarp.h		aarp general header
	ethertalk.c	ethertalk routines
	ethertalk.h	ethertalk defintiions

Process (de)multiplexing
	mpxddp.h	ddp to local process (de)multiplexing
	kip_mpx.c	ddp to local process via modified kip scheme

Protocol Interfaces
	proto_intf.h	protocol interface
	dlip.c		dli: protocol interface
	snitp.c		snit: protocol interface

Auxillary routines:
	hash.c		hash routines
	hash.h		hash routine header
	log.c		log routines
	log.h		logging header

SHAR_EOF
if test 915 -ne "`wc -c < 'whatiswhat'`"
then
	echo shar: "error transmitting 'whatiswhat'" '(should have been 915 characters)'
fi
chmod 640 'whatiswhat'
fi
exit 0
#	End of shell archive
