#include "snmpsock.h"
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>

#define MAXPACKSIZE 10240

unsigned int maxaddrlen=sizeof(sockaddr);

struct request_t{
//   int magic1;
  int len;
  char *addr;
  pthread_cond_t req_cv;
  request_t *next;
  request_t *prev;

  request_t(int length, char *address):len(length),addr(address),
    next(NULL),prev(NULL),buf(NULL){ //,magic1(0xbadcab88),magic2(0xdeadbeef){
    assert(addr!=NULL || addr!=(char*)1);
    pthread_cond_init(&req_cv,NULL);
  }

  //filled in on the return
  unsigned char *buf;
  int buflen;
  int errnum;
//   char padding[200];
//   int magic2;
};

void unlink(request_t **head,request_t *cur){
  if(cur->prev)
    cur->prev->next=cur->next;
  if(cur->next)
    cur->next->prev=cur->prev;
  if(cur==*head)
    *head=cur->next;
}

pthread_mutex_t pending_m=PTHREAD_MUTEX_INITIALIZER;
request_t *pending=NULL;

// void check_plist(char *file,unsigned int line){
//   fprintf(stderr,"debug: thread: %lu checking at %s %u ",pthread_self(),file,
// 	  line);
//   pthread_mutex_lock(&pending_m);
//   for(request_t *cur=pending;cur!=NULL;cur=cur->next){
//     fprintf(stderr,"0x%x",cur);
//     assert(cur->magic1==0xbadcab88);
//     fputs("->",stderr);
//   }
//   fputc('\n',stderr);
//   pthread_mutex_unlock(&pending_m);
// }

void *receiver(void *sockp){
  int sock=*(int*)sockp;
  int readcnt;
  unsigned int fromlen;
  for(;;){
    sockaddr_in from;
    memset(&from,0,sizeof(from));
    fromlen=sizeof(sockaddr_in);
    unsigned char *buf=new unsigned char[MAXPACKSIZE];
    assert(buf);
    fd_set rfds;
    timeval tv;
    int retval;
    FD_ZERO(&rfds);
    FD_SET(sock,&rfds);
    tv.tv_sec=5;
    tv.tv_usec=0;
    assert((retval=select(sock+1,&rfds,NULL,NULL,&tv))!=-1);
    pthread_testcancel();

    // check every 5 seconds to see if we were cancelled
    if(retval!=1 || !FD_ISSET(sock,&rfds))
      continue;

    // actually got some data
    readcnt=recvfrom(sock,buf,MAXPACKSIZE,0,(sockaddr*)&from,&fromlen);
    if(readcnt==-1){
      if(errno==ECONNREFUSED)
	continue; // just ignore those pesky icmp unreachable errors
      perror("bad read in reciever thread");
      exit(5);
    }

    pthread_mutex_lock(&pending_m);
    request_t *cur;
    for(cur=pending;cur!=NULL;cur=cur->next){
//       assert(cur->magic1==0xbadcab88);
//       assert(cur->magic2==0xdeadbeef);
      if(!memcmp(&from.sin_addr,cur->addr,cur->len))
	break;
    }
    // now cur equals either null i.e. not found or the correct element
    if(cur==NULL){
      fprintf(stderr,"Warning: stray packet recieved from %u.%u.%u.%u\n",
	      ((char*)&from.sin_addr)[0]&0xff,((char*)&from.sin_addr)[1]&0xff,
	      ((char*)&from.sin_addr)[2]&0xff,((char*)&from.sin_addr)[3]&0xff);
	      
      pthread_mutex_unlock(&pending_m);
      continue;
    }
    cur->buf=buf;
    cur->buflen=readcnt;
    cur->errnum=errno;

    //unlink everything
    unlink(&pending,cur);
      
    pthread_cond_signal(&cur->req_cv);
      
    pthread_mutex_unlock(&pending_m);
  }
}

SNMP_socket::SNMP_socket(int tmo, int rt, int pt):timeout(tmo),retries(rt),
  port(pt){
  // create socket
  struct protoent *pe;
  struct servent *se;
  
  assert(pe=getprotobyname("udp"));
  assert((sock=socket(AF_INET,SOCK_DGRAM,pe->p_proto))!=-1);

  if(port==0){
    se = getservbyname("snmp","udp");
    if (NULL != se){
      port=se->s_port;
      endservent();
    } else
      port = 161; /* Fall back to hardcoded value */
  }  else 
    port=htons(port);

  // create listening thread
  assert(pthread_create(&listening_thr,NULL,receiver,&sock)==0);
}

SNMP_socket::~SNMP_socket(){
  // zap listening thread 
  pthread_cancel(listening_thr);
  pthread_join(listening_thr,NULL);
  // close socket
  close(sock);
}

unsigned char *SNMP_socket::call(int len,int type,char *addr,char *data,
				 int &buflen){
  request_t curreq(len,addr);
  if(len>maxaddrlen)
    maxaddrlen=len;

  // send packet
  struct sockaddr_in sin;
  memset((caddr_t)&sin,0,sizeof(sin));
  sin.sin_family=type,
  sin.sin_port=port;
  memcpy((caddr_t)&sin.sin_addr,addr,len);
  
  // insert it on the list
  pthread_mutex_lock(&pending_m);
  curreq.next=pending;
  if(pending)
    pending->prev=&curreq;
  pending=&curreq;

  //while there are still retries
  int rt;
  for(rt=retries;rt>=0;rt--){
  retry:
    int sendcnt=sendto(sock,data,buflen,0,(sockaddr*)&sin,
		       sizeof(sockaddr_in));
    /* work around potential problem where ICMP port unreachable 
       propegates up IP stack and can appear on socket even 
       though problem is not with this particular packet. */
    if(sendcnt==-1 && errno==ECONNREFUSED)
      goto retry;
    assert(sendcnt==-1 || sendcnt==buflen);
    if(sendcnt==-1){
      perror("Error sending packet");
      // remove from list and return error
      buflen=errno;
      pthread_mutex_lock(&pending_m);
      unlink(&pending,&curreq);
      pthread_mutex_unlock(&pending_m);
      return NULL;
    }
    // wait on condition variable
    timespec tv;
    tv.tv_sec=time(NULL)+timeout;
    tv.tv_nsec=0;
    
    int waitret=pthread_cond_timedwait(&curreq.req_cv,&pending_m,&tv);
    if(waitret!=ETIMEDOUT)
      break;
  }
  if(rt<0)
    unlink(&pending,&curreq);
  pthread_mutex_unlock(&pending_m);

  // kick out bad data
  if(curreq.buflen==-1){
    delete curreq.buf;
    buflen=curreq.errnum;
    return NULL;
  }
  
  buflen=curreq.buflen;
  return curreq.buf;
}


syntax highlighted by Code2HTML, v. 0.9.1