/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Author : Guillaume TEISSIER from FTR&D 02/02/2006
 */
#include <pcap.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <net/ethernet.h>
#include <assert.h>
#include <string.h>

#include "prepare_pcap.h"

inline int check(u_int16_t *buffer, int len){
  int sum;
  sum = 0;
  while (len > 1)
  {
    sum += *buffer++;
    len -= 2;
  }
  if (len == 1)
  {
    sum += *(u_int16_t *)buffer;
  }
  return sum;
}

inline u_int16_t checksum_carry(int s) {
	int s_c = (s >> 16) + (s & 0xffff);
	return (~(s_c + (s_c >> 16)) & 0xffff);
}

char errbuf[PCAP_ERRBUF_SIZE];

/* prepare a pcap file
 * keep RTP and get rid of IPv{4,6}/UDP part
 * this part should be set by kernel
 */
int prepare_pkts(char *file, pcap_pkts *pkts) {
  pcap_t *pcap;
  struct pcap_pkthdr *pkthdr = NULL;
  u_char *pktdata = NULL;
  int n_pkts = 0;
  u_long max_length = 0;
  u_int16_t base = 0xffff;
  u_int16_t *chk_buffer;
  u_long pktlen;
  pcap_pkt *pkt_index;
  struct ether_header *ethhdr;
  struct iphdr *iphdr;
  struct ip6_hdr *ip6hdr;
  struct udphdr *udphdr;

  // to be suppressed
  int check_s = 0;
  int diff;

  pkts->pkts = NULL;

  pcap = pcap_open_offline(file, errbuf);
  assert(pcap);

#if HAVE_PCAP_NEXT_EX
  while (pcap_next_ex (pcap, &pkthdr, (const u_char **) &pktdata) == 1)
  {
#else
  pkthdr = malloc (sizeof (*pkthdr));
  assert (pkthdr);
  while ((pktdata = pcap_next (pcap, pkthdr)) != NULL)
  {
#endif
    ethhdr = (struct ether_header *)pktdata;
    if (ntohs(ethhdr->ether_type) != ETHERTYPE_IP) {
      fprintf(stderr, "Ignoring non IP packet!\n");
      continue;
    }
    iphdr = (struct iphdr *)((char *)ethhdr + sizeof(*ethhdr));
    if (iphdr && iphdr->version == 6) {
      //ipv6
      pktlen = (u_long) pkthdr->len - sizeof(*ethhdr) - sizeof(*ip6hdr);
      ip6hdr = (struct ip6_hdr *)iphdr;
      udphdr = (struct udphdr *)((char *)ip6hdr + sizeof(*ip6hdr));
    } else {
      //ipv4
      pktlen = (u_long) pkthdr->len - sizeof(*ethhdr) - sizeof(*iphdr);
      if (iphdr->protocol != IPPROTO_UDP) {
        fprintf(stderr, "Ignoring non UDP packet!\n");
        continue;
      }
      udphdr = (struct udphdr *)((char *)iphdr + (iphdr->ihl << 2));
    }
    if (pktlen > PCAP_MAXPACKET) {
      fprintf(stderr, "Packet size is too big!\nRecompile with bigger PCAP_MAXPACKET in %s!\n", "prepare_pcap.h");
      abort();
    }
    pkts->pkts = realloc(pkts->pkts, sizeof(*(pkts->pkts)) * (n_pkts + 1));
    assert(pkts->pkts);
    pkt_index = pkts->pkts + n_pkts;
    pkt_index->pktlen = pktlen;
    pkt_index->ts = pkthdr->ts;
    pkt_index->data = malloc(pktlen);
    assert(pkt_index->data);
    memcpy(pkt_index->data, udphdr, pktlen);
    if (iphdr && iphdr->version == 6) {
      //ipv6, no checksum
      pkt_index->partial_check = 0;
    } else {
      //ipv4
      check_s = udphdr->check;
      udphdr->check = 0;
      // compute a partial udp checksum
      // not including port that will be changed
      // when sending RTP
      pkt_index->partial_check = check((u_int16_t *) &udphdr->len, pktlen - 4) + ntohs(IPPROTO_UDP + pktlen);
      diff = pkt_index->partial_check + check((u_int16_t *)&iphdr->saddr, 8) + check((u_int16_t *) &udphdr->source, 4);
      diff = check_s - checksum_carry(diff);
      if (diff)
        fprintf(stderr, "CANNOT rebuild checksum: %s [%d] size = %d\n", file, (pkt_index - pkts->pkts), pktlen);
    }
    if (max_length < pktlen)
      max_length = pktlen;
    if (base > ntohs(udphdr->dest))
      base = ntohs(udphdr->dest);
    n_pkts++;
  }
  pkts->max = pkts->pkts + n_pkts;
  pkts->max_length = max_length;
  pkts->base = base;
  fprintf(stderr, "In pcap %s, npkts %d\nmax pkt length %d\nbase port %d\n", file, n_pkts, max_length, base);
  pcap_close(pcap);
}

void free_pkts(pcap_pkts *pkts) {
  pcap_pkt *pkt_index;
  while (pkt_index < pkts->max) {
    free(pkt_index->data);
  }
  free(pkts->pkts);
}
