
#include <pcap.h>
#include <unistd.h>
#ifndef __OpenBSD__
#include <pcap-bpf.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "network.h"
#include "proto_mod.h"
#include "debug.h"

#include "pcap_logger.h"
#include "logpkt.h"

#define DFLT_FILEMODE 0666

static int init_pcap_logger PROTO_LIST((void *data));
static int deinit_pcap_logger PROTO_LIST(());
static int create_pcap_logger PROTO_LIST((proto_obj * *objp,
                                          struct sockaddr_storage *i_addr,
                                          u_short i_port,
                                          struct sockaddr_storage *r_addr,
                                          u_short r_port,
                                          struct timeval *base_time));
static int destroy_pcap_logger PROTO_LIST((proto_obj * *objp));
static int data_pcap_logger PROTO_LIST(
    (proto_obj * _obj, unsigned char *data, unsigned int len, int dir));
static int close_pcap_logger PROTO_LIST(
    (proto_obj * _obj, unsigned char *data, unsigned int len, int dir));

int pcap_fd = -1;
static uint8_t content_pcap_src_ether[ETHER_ADDR_LEN] = {0x02, 0x00, 0x00,
                                                         0x11, 0x11, 0x11};
static uint8_t content_pcap_dst_ether[ETHER_ADDR_LEN] = {0x02, 0x00, 0x00,
                                                         0x22, 0x22, 0x22};

static int init_pcap_logger(void *data) {
  char *pcap_outfile = (char *)data;
  pcap_fd = open(pcap_outfile, O_RDWR | O_CREAT, DFLT_FILEMODE);
  if(pcap_fd == -1) {
    // printf("Failed to open pcap '%s' for writing\n", pcap_outfile);
    return -1;
  }
  if(logpkt_pcap_open_fd(pcap_fd) == -1) {
    // printf("Failed to prepare '%s' for PCAP writing\n", pcap_outfile);
    close(pcap_fd);
    pcap_fd = -1;
    return -1;
  }
  return 0;
}

static int deinit_pcap_logger(void) {
#if defined(_POSIX_SYNCHRONIZED_IO) && (_POSIX_SYNCHRONIZED_IO > 0)
    fdatasync(pcap_fd);
#else
    fsync(pcap_fd);
#endif
  close(pcap_fd);
  return 0;
}

static int create_pcap_logger(proto_obj **objp,
                              struct sockaddr_storage *i_addr,
                              u_short i_port,
                              struct sockaddr_storage *r_addr,
                              u_short r_port,
                              struct timeval *base_time) {
  int _status;
  logpkt_ctx_t *pcap_obj = 0;
  struct sockaddr_in src_addr, dst_addr;
  uint32_t timestamp_sec, timestamp_usec;

  timestamp_sec = base_time->tv_sec;
  timestamp_usec = base_time->tv_usec;

  if(!(pcap_obj = (logpkt_ctx_t *)calloc(1, sizeof(logpkt_ctx_t))))
    ABORT(R_NO_MEMORY);

  // src_addr.sin_family = AF_INET;
  // src_addr.sin_addr = *i_addr;
  memcpy(&src_addr, i_addr, sizeof(struct sockaddr_in));
  src_addr.sin_port = htons(i_port);

  // dst_addr.sin_family = AF_INET;
  // dst_addr.sin_addr = *r_addr;
  memcpy(&dst_addr, r_addr, sizeof(struct sockaddr_in));
  dst_addr.sin_port = htons(r_port);

  logpkt_ctx_init(pcap_obj, NULL, 0, content_pcap_src_ether,
                  content_pcap_dst_ether, (const struct sockaddr *)&src_addr,
                  sizeof(src_addr), (const struct sockaddr *)&dst_addr,
                  sizeof(dst_addr),
                  &timestamp_sec,
                  &timestamp_usec);
  *objp = (proto_obj *)pcap_obj;
  _status = 0;
abort:
  if(_status) {
    destroy_pcap_logger((proto_obj **)&pcap_obj);
  }
  return _status;
}

static int destroy_pcap_logger(proto_obj **objp) {
  logpkt_ctx_t *pcap_obj;

  if(!objp || !*objp)
    return 0;

  pcap_obj = (logpkt_ctx_t *)*objp;

  free(pcap_obj);
  *objp = 0;

  return 0;
}

static int data_pcap_logger(proto_obj *_obj,
                            unsigned char *data,
                            unsigned int len,
                            int dir) {
  logpkt_ctx_t *pcap_obj = (logpkt_ctx_t *)_obj;
  int direction;
  int status;

  if(dir == DIR_I2R)
    direction = LOGPKT_REQUEST;
  else
    direction = LOGPKT_RESPONSE;

  status = logpkt_write_payload(pcap_obj, pcap_fd, direction, data, len);

  return status;
}

int close_pcap_logger(proto_obj *_obj,
                      unsigned char *data,
                      unsigned int len,
                      int dir) {
  logpkt_ctx_t *pcap_obj = (logpkt_ctx_t *)_obj;
  int direction;
  int status;

  if(dir == DIR_I2R)
    direction = LOGPKT_REQUEST;
  else
    direction = LOGPKT_RESPONSE;

  status = logpkt_write_close(pcap_obj, pcap_fd, direction);

  return status;
}

static struct logger_mod_vtbl_ pcap_vtbl = {
    init_pcap_logger,    deinit_pcap_logger, create_pcap_logger,
    destroy_pcap_logger, data_pcap_logger,   close_pcap_logger,
};

struct logger_mod_ pcap_mod = {"PCAP", &pcap_vtbl};
