/* Copyright 2002 Yon Uriarte and Jeff Dike
 * Licensed under the GPL
 */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/signal.h>
#include "switch.h"
#include "hash.h"

#define HASH_SIZE 128
#define HASH_MOD 11

struct hash_entry {
  struct hash_entry *next;
  struct hash_entry *prev;
  time_t last_seen;
  void *port;
  unsigned char dst[ETH_ALEN];
};

static struct hash_entry *h[HASH_SIZE];

static int calc_hash(unsigned char *src)
{
  return ((*(u_int32_t *) &src[0] % HASH_MOD) ^ src[4] ^ src[5] ) % HASH_SIZE ;
}

static struct hash_entry *find_entry(unsigned char *dst)
{
  struct hash_entry *e;
  int k = calc_hash(dst);

  for(e = h[k]; e; e = e->next){
    if(!memcmp(&e->dst, dst, ETH_ALEN)) return(e);
  }
  return(NULL);  
}

void *find_in_hash(unsigned char *dst)
{
  struct hash_entry *e = find_entry(dst);
  if(e == NULL) return(NULL);
  return(e->port);
}

void insert_into_hash(unsigned char *src, void *port)
{
  struct hash_entry *new;
  int k = calc_hash(src);

  new = find_in_hash(src);
  if(new != NULL) return;

  new = malloc(sizeof(*new));
  if(new == NULL){
    perror("Failed to malloc hash entry");
    return;
  }

  memcpy(&new->dst, src, ETH_ALEN );
  if(h[k] != NULL) h[k]->prev = new;
  new->next = h[k];
  new->prev = NULL;
  new->port = port;
  new->last_seen = 0;
  h[k] = new;
}

void update_entry_time(unsigned char *src)
{
  struct hash_entry *e;

  e = find_entry(src);
  if(e == NULL) return;
  e->last_seen = time(NULL);
}

static void delete_hash_entry(struct hash_entry *old)
{
  int k = calc_hash(old->dst);

  if(old->prev != NULL) old->prev->next = old->next;
  if(old->next != NULL) old->next->prev = old->prev;
  if(h[k] == old) h[k] = old->next;
  free(old);
}

void delete_hash(unsigned char *dst)
{
  struct hash_entry *old = find_entry(dst);

  if(old == NULL) return;
  delete_hash_entry(old);
}

static void for_all_hash(void (*f)(struct hash_entry *, void *), void *arg)
{
  int i;
  struct hash_entry *e, *next;

  for(i = 0; i < HASH_SIZE; i++){
    for(e = h[i]; e; e = next){
      next = e->next;
      (*f)(e, arg);
    }
  }
}

struct printer {
  time_t now;
  char *(*port_id)(void *);
};

static void print_hash_entry(struct hash_entry *e, void *arg)
{
  struct printer *p = arg;

  printf("Hash: %d Addr: %02x:%02x:%02x:%02x:%02x:%02x to port: %s  " 
	 "age %ld secs\n", calc_hash(e->dst),
	 e->dst[0], e->dst[1], e->dst[2], e->dst[3], e->dst[4], e->dst[5],
	 (*p->port_id)(e->port), (int) p->now - e->last_seen);
}

void print_hash(char *(*port_id)(void *))
{
  struct printer p = ((struct printer) { now : 		time(NULL),
					 port_id :	port_id });

  for_all_hash(print_hash_entry, &p);
}

#define GC_INTERVAL 2
#define GC_EXPIRE 100

static void gc(struct hash_entry *e, void *now)
{
  time_t t = *(time_t *) now;

  if(e->last_seen + GC_EXPIRE < t)
    delete_hash_entry(e);
}

static void sig_alarm(int sig)
{
  struct itimerval it;
  time_t t = time(NULL);
  for_all_hash(&gc, &t);

  it.it_value.tv_sec = GC_INTERVAL;
  it.it_value.tv_usec = 0 ;
  it.it_interval.tv_sec = 0;
  it.it_interval.tv_usec = 0 ;
  setitimer(ITIMER_REAL, &it, NULL);
}

void hash_init(void)
{
  struct sigaction sa;

  sa.sa_handler = sig_alarm;
  sa.sa_flags = SA_RESTART;
  if(sigaction(SIGALRM, &sa, NULL) < 0){
    perror("Setting handler for SIGALRM");
    return;
  }
  kill(getpid(), SIGALRM);
}
