#include "erl_nif.h"
#include "bpf.h"
#include <errno.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>

/* copied from otp/erts/emulator/beam/erl_posix_str.c */
char *
erl_errno_id(error)
     int error;			/* Posix error number (as from errno). */
{
  switch (error) {
#ifdef E2BIG
  case E2BIG: return "e2big";
#endif
#ifdef EACCES
  case EACCES: return "eacces";
#endif
#ifdef EADDRINUSE
  case EADDRINUSE: return "eaddrinuse";
#endif
#ifdef EADDRNOTAVAIL
  case EADDRNOTAVAIL: return "eaddrnotavail";
#endif
#ifdef EADV
  case EADV: return "eadv";
#endif
#ifdef EAFNOSUPPORT
  case EAFNOSUPPORT: return "eafnosupport";
#endif
#ifdef EAGAIN
  case EAGAIN: return "eagain";
#endif
#ifdef EALIGN
  case EALIGN: return "ealign";
#endif
#if defined(EALREADY) && (!defined(EBUSY) || (EALREADY != EBUSY ))
  case EALREADY: return "ealready";
#endif
#ifdef EBADE
  case EBADE: return "ebade";
#endif
#ifdef EBADF
  case EBADF: return "ebadf";
#endif
#ifdef EBADFD
  case EBADFD: return "ebadfd";
#endif
#ifdef EBADMSG
  case EBADMSG: return "ebadmsg";
#endif
#ifdef EBADR
  case EBADR: return "ebadr";
#endif
#ifdef EBADRPC
  case EBADRPC: return "ebadrpc";
#endif
#ifdef EBADRQC
  case EBADRQC: return "ebadrqc";
#endif
#ifdef EBADSLT
  case EBADSLT: return "ebadslt";
#endif
#ifdef EBFONT
  case EBFONT: return "ebfont";
#endif
#ifdef EBUSY
  case EBUSY: return "ebusy";
#endif
#ifdef ECHILD
  case ECHILD: return "echild";
#endif
#ifdef ECHRNG
  case ECHRNG: return "echrng";
#endif
#ifdef ECOMM
  case ECOMM: return "ecomm";
#endif
#ifdef ECONNABORTED
  case ECONNABORTED: return "econnaborted";
#endif
#ifdef ECONNREFUSED
  case ECONNREFUSED: return "econnrefused";
#endif
#ifdef ECONNRESET
  case ECONNRESET: return "econnreset";
#endif
#if defined(EDEADLK) && (!defined(EWOULDBLOCK) || (EDEADLK != EWOULDBLOCK))
  case EDEADLK: return "edeadlk";
#endif
#if defined(EDEADLOCK) && (!defined(EDEADLK) || (EDEADLOCK != EDEADLK))
  case EDEADLOCK: return "edeadlock";
#endif
#ifdef EDESTADDRREQ
  case EDESTADDRREQ: return "edestaddrreq";
#endif
#ifdef EDIRTY
  case EDIRTY: return "edirty";
#endif
#ifdef EDOM
  case EDOM: return "edom";
#endif
#ifdef EDOTDOT
  case EDOTDOT: return "edotdot";
#endif
#ifdef EDQUOT
  case EDQUOT: return "edquot";
#endif
#ifdef EDUPPKG
  case EDUPPKG: return "eduppkg";
#endif
#ifdef EEXIST
  case EEXIST: return "eexist";
#endif
#ifdef EFAULT
  case EFAULT: return "efault";
#endif
#ifdef EFTYPE
  case EFTYPE: return "eftype";
#endif
#ifdef EFBIG
  case EFBIG: return "efbig";
#endif
#ifdef EHOSTDOWN
  case EHOSTDOWN: return "ehostdown";
#endif
#ifdef EHOSTUNREACH
  case EHOSTUNREACH: return "ehostunreach";
#endif
#if defined(EIDRM) && (!defined(EINPROGRESS) || (EIDRM != EINPROGRESS))
  case EIDRM: return "eidrm";
#endif
#ifdef EILSEQ
  case EILSEQ: return "eilseq";
#endif
#ifdef EINIT
  case EINIT: return "einit";
#endif
#ifdef EINPROGRESS
  case EINPROGRESS: return "einprogress";
#endif
#ifdef EINTR
  case EINTR: return "eintr";
#endif
#ifdef EINVAL
  case EINVAL: return "einval";
#endif
#ifdef EIO
  case EIO: return "eio";
#endif
#ifdef EISCONN
  case EISCONN: return "eisconn";
#endif
#ifdef EISDIR
  case EISDIR: return "eisdir";
#endif
#ifdef EISNAME
  case EISNAM: return "eisnam";
#endif
#ifdef ELBIN
  case ELBIN: return "elbin";
#endif
#ifdef EL2HLT
  case EL2HLT: return "el2hlt";
#endif
#ifdef EL2NSYNC
  case EL2NSYNC: return "el2nsync";
#endif
#ifdef EL3HLT
  case EL3HLT: return "el3hlt";
#endif
#ifdef EL3RST
  case EL3RST: return "el3rst";
#endif
#ifdef ELIBACC
  case ELIBACC: return "elibacc";
#endif
#ifdef ELIBBAD
  case ELIBBAD: return "elibbad";
#endif
#ifdef ELIBEXEC
  case ELIBEXEC: return "elibexec";
#endif
#ifdef ELIBMAX
  case ELIBMAX: return "elibmax";
#endif
#ifdef ELIBSCN
  case ELIBSCN: return "elibscn";
#endif
#ifdef ELNRNG
  case ELNRNG: return "elnrng";
#endif
#if defined(ELOOP) && (!defined(ENOENT) || (ELOOP != ENOENT))
  case ELOOP: return "eloop";
#endif
#ifdef EMFILE
  case EMFILE: return "emfile";
#endif
#ifdef EMLINK
  case EMLINK: return "emlink";
#endif
#ifdef EMSGSIZE
  case EMSGSIZE: return "emsgsize";
#endif
#ifdef EMULTIHOP
  case EMULTIHOP: return "emultihop";
#endif
#ifdef ENAMETOOLONG
  case ENAMETOOLONG: return "enametoolong";
#endif
#ifdef ENAVAIL
  case ENAVAIL: return "enavail";
#endif
#ifdef ENET
  case ENET: return "enet";
#endif
#ifdef ENETDOWN
  case ENETDOWN: return "enetdown";
#endif
#ifdef ENETRESET
  case ENETRESET: return "enetreset";
#endif
#ifdef ENETUNREACH
  case ENETUNREACH: return "enetunreach";
#endif
#ifdef ENFILE
  case ENFILE: return "enfile";
#endif
#ifdef ENOANO
  case ENOANO: return "enoano";
#endif
#if defined(ENOBUFS) && (!defined(ENOSR) || (ENOBUFS != ENOSR))
  case ENOBUFS: return "enobufs";
#endif
#ifdef ENOCSI
  case ENOCSI: return "enocsi";
#endif
#if defined(ENODATA) && (!defined(ECONNREFUSED) || (ENODATA != ECONNREFUSED))
  case ENODATA: return "enodata";
#endif
#ifdef ENODEV
  case ENODEV: return "enodev";
#endif
#ifdef ENOENT
  case ENOENT: return "enoent";
#endif
#ifdef ENOEXEC
  case ENOEXEC: return "enoexec";
#endif
#ifdef ENOLCK
  case ENOLCK: return "enolck";
#endif
#ifdef ENOLINK
  case ENOLINK: return "enolink";
#endif
#ifdef ENOMEM
  case ENOMEM: return "enomem";
#endif
#ifdef ENOMSG
  case ENOMSG: return "enomsg";
#endif
#ifdef ENONET
  case ENONET: return "enonet";
#endif
#ifdef ENOPKG
  case ENOPKG: return "enopkg";
#endif
#ifdef ENOPROTOOPT
  case ENOPROTOOPT: return "enoprotoopt";
#endif
#ifdef ENOSPC
  case ENOSPC: return "enospc";
#endif
#if defined(ENOSR) && (!defined(ENAMETOOLONG) || (ENAMETOOLONG != ENOSR))
  case ENOSR: return "enosr";
#endif
#if defined(ENOSTR) && (!defined(ENOTTY) || (ENOTTY != ENOSTR))
  case ENOSTR: return "enostr";
#endif
#ifdef ENOSYM
  case ENOSYM: return "enosym";
#endif
#ifdef ENOSYS
  case ENOSYS: return "enosys";
#endif
#ifdef ENOTBLK
  case ENOTBLK: return "enotblk";
#endif
#ifdef ENOTCONN
  case ENOTCONN: return "enotconn";
#endif
#ifdef ENOTDIR
  case ENOTDIR: return "enotdir";
#endif
#if defined(ENOTEMPTY) && (!defined(EEXIST) || (ENOTEMPTY != EEXIST))
  case ENOTEMPTY: return "enotempty";
#endif
#ifdef ENOTNAM
  case ENOTNAM: return "enotnam";
#endif
#ifdef ENOTSOCK
  case ENOTSOCK: return "enotsock";
#endif
#ifdef ENOTSUP
  case ENOTSUP: return "enotsup";
#endif
#ifdef ENOTTY
  case ENOTTY: return "enotty";
#endif
#ifdef ENOTUNIQ
  case ENOTUNIQ: return "enotuniq";
#endif
#ifdef ENXIO
  case ENXIO: return "enxio";
#endif
#if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || (EOPNOTSUPP != ENOTSUP))
  case EOPNOTSUPP: return "eopnotsupp";
#endif
#ifdef EOVERFLOW
  case EOVERFLOW: return "eoverflow";
#endif
#ifdef EPERM
  case EPERM: return "eperm";
#endif
#if defined(EPFNOSUPPORT) && (!defined(ENOLCK) || (ENOLCK != EPFNOSUPPORT))
  case EPFNOSUPPORT: return "epfnosupport";
#endif
#ifdef EPIPE
  case EPIPE: return "epipe";
#endif
#ifdef EPROCLIM
  case EPROCLIM: return "eproclim";
#endif
#ifdef EPROCUNAVAIL
  case EPROCUNAVAIL: return "eprocunavail";
#endif
#ifdef EPROGMISMATCH
  case EPROGMISMATCH: return "eprogmismatch";
#endif
#ifdef EPROGUNAVAIL
  case EPROGUNAVAIL: return "eprogunavail";
#endif
#ifdef EPROTO
  case EPROTO: return "eproto";
#endif
#ifdef EPROTONOSUPPORT
  case EPROTONOSUPPORT: return "eprotonosupport";
#endif
#ifdef EPROTOTYPE
  case EPROTOTYPE: return "eprototype";
#endif
#ifdef ERANGE
  case ERANGE: return "erange";
#endif
#if defined(EREFUSED) && (!defined(ECONNREFUSED) || (EREFUSED != ECONNREFUSED))
  case EREFUSED: return "erefused";
#endif
#ifdef EREMCHG
  case EREMCHG: return "eremchg";
#endif
#ifdef EREMDEV
  case EREMDEV: return "eremdev";
#endif
#ifdef EREMOTE
  case EREMOTE: return "eremote";
#endif
#ifdef EREMOTEIO
  case EREMOTEIO: return "eremoteio";
#endif
#ifdef EREMOTERELEASE
  case EREMOTERELEASE: return "eremoterelease";
#endif
#ifdef EROFS
  case EROFS: return "erofs";
#endif
#ifdef ERPCMISMATCH
  case ERPCMISMATCH: return "erpcmismatch";
#endif
#ifdef ERREMOTE
  case ERREMOTE: return "erremote";
#endif
#ifdef ESHUTDOWN
  case ESHUTDOWN: return "eshutdown";
#endif
#ifdef ESOCKTNOSUPPORT
  case ESOCKTNOSUPPORT: return "esocktnosupport";
#endif
#ifdef ESPIPE
  case ESPIPE: return "espipe";
#endif
#ifdef ESRCH
  case ESRCH: return "esrch";
#endif
#ifdef ESRMNT
  case ESRMNT: return "esrmnt";
#endif
#ifdef ESTALE
  case ESTALE: return "estale";
#endif
#ifdef ESUCCESS
  case ESUCCESS: return "esuccess";
#endif
#if defined(ETIME) && (!defined(ELOOP) || (ETIME != ELOOP))
  case ETIME: return "etime";
#endif
#if defined(ETIMEDOUT) && (!defined(ENOSTR) || (ETIMEDOUT != ENOSTR)) && (!defined(EAGAIN) || (ETIMEDOUT != EAGAIN)) && (!defined(WSAETIMEDOUT) || (ETIMEDOUT != WSAETIMEDOUT))
  case ETIMEDOUT: return "etimedout";
#endif
#ifdef ETOOMANYREFS
  case ETOOMANYREFS: return "etoomanyrefs";
#endif
#ifdef ETXTBSY
  case ETXTBSY: return "etxtbsy";
#endif
#ifdef EUCLEAN
  case EUCLEAN: return "euclean";
#endif
#ifdef EUNATCH
  case EUNATCH: return "eunatch";
#endif
#ifdef EUSERS
  case EUSERS: return "eusers";
#endif
#ifdef EVERSION
  case EVERSION: return "eversion";
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) && (!defined(WSAEWOULDBLOCK) || (EWOULDBLOCK != WSAEWOULDBLOCK))
  case EWOULDBLOCK: return "ewouldblock";
#endif
#ifdef EXDEV
  case EXDEV: return "exdev";
#endif
#ifdef EXFULL
  case EXFULL: return "exfull";
#endif
#ifdef WSAEINTR
  case WSAEINTR: return "eintr";
#endif
#ifdef WSAEBADF
  case WSAEBADF: return "ebadf";
#endif
#ifdef WSAEACCES
  case WSAEACCES: return "eacces";
#endif
#ifdef WSAEFAULT
  case WSAEFAULT: return "efault";
#endif
#ifdef WSAEINVAL
  case WSAEINVAL: return "einval";
#endif
#ifdef WSAEMFILE
  case WSAEMFILE: return "emfile";
#endif
#ifdef WSAEWOULDBLOCK  
  case WSAEWOULDBLOCK: return "ewouldblock";
#endif
#ifdef WSAEINPROGRESS  
  case WSAEINPROGRESS: return "einprogress";
#endif
#ifdef WSAEALREADY     
  case WSAEALREADY: return "ealready";
#endif
#ifdef WSAENOTSOCK     
  case WSAENOTSOCK: return "enotsock";
#endif
#ifdef WSAEDESTADDRREQ 
  case WSAEDESTADDRREQ: return "edestaddrreq";
#endif
#ifdef WSAEMSGSIZE     
  case WSAEMSGSIZE: return "emsgsize";
#endif
#ifdef WSAEPROTOTYPE   
  case WSAEPROTOTYPE: return "eprototype";
#endif
#ifdef WSAENOPROTOOPT  
  case WSAENOPROTOOPT: return "enoprotoopt";
#endif
#ifdef WSAEPROTONOSUPPORT
  case WSAEPROTONOSUPPORT: return "eprotonosupport";
#endif
#ifdef WSAESOCKTNOSUPPORT
  case WSAESOCKTNOSUPPORT: return "esocktnosupport";
#endif
#ifdef WSAEOPNOTSUPP   
  case WSAEOPNOTSUPP: return "eopnotsupp";
#endif
#ifdef WSAEPFNOSUPPORT 
  case WSAEPFNOSUPPORT: return "epfnosupport";
#endif
#ifdef WSAEAFNOSUPPORT 
  case WSAEAFNOSUPPORT: return "eafnosupport";
#endif
#ifdef WSAEADDRINUSE   
  case WSAEADDRINUSE: return "eaddrinuse";
#endif
#ifdef WSAEADDRNOTAVAIL
  case WSAEADDRNOTAVAIL: return "eaddrnotavail";
#endif
#ifdef WSAENETDOWN    
  case WSAENETDOWN: return "enetdown";
#endif
#ifdef WSAENETUNREACH 
  case WSAENETUNREACH: return "enetunreach";
#endif
#ifdef WSAENETRESET   
  case WSAENETRESET: return "enetreset";
#endif
#ifdef WSAECONNABORTED
  case WSAECONNABORTED: return "econnaborted";
#endif
#ifdef WSAECONNRESET  
  case WSAECONNRESET: return "econnreset";
#endif
#ifdef WSAENOBUFS     
  case WSAENOBUFS: return "enobufs";
#endif
#ifdef WSAEISCONN     
  case WSAEISCONN: return "eisconn";
#endif
#ifdef WSAENOTCONN    
  case WSAENOTCONN: return "enotconn";
#endif
#ifdef WSAESHUTDOWN   
  case WSAESHUTDOWN: return "eshutdown";
#endif
#ifdef WSAETOOMANYREFS
  case WSAETOOMANYREFS: return "etoomanyrefs";
#endif
#ifdef WSAETIMEDOUT   
  case WSAETIMEDOUT: return "etimedout";
#endif
#ifdef WSAECONNREFUSED
  case WSAECONNREFUSED: return "econnrefused";
#endif
#ifdef WSAELOOP
  case WSAELOOP: return "eloop";
#endif
#ifdef WSAENAMETOOLONG
  case WSAENAMETOOLONG: return "enametoolong";
#endif
#ifdef WSAEHOSTDOWN
  case WSAEHOSTDOWN: return "ehostdown";
#endif
#ifdef WSAEHOSTUNREACH
  case WSAEHOSTUNREACH: return "ehostunreach";
#endif
#ifdef WSAENOTEMPTY
  case WSAENOTEMPTY: return "enotempty";
#endif
#ifdef WSAEPROCLIM
  case WSAEPROCLIM: return "eproclim";
#endif
#ifdef WSAEUSERS
  case WSAEUSERS: return "eusers";
#endif
#ifdef WSAEDQUOT
  case WSAEDQUOT: return "edquot";
#endif
#ifdef WSAESTALE
  case WSAESTALE: return "estale";
#endif
#ifdef WSAEREMOTE
  case WSAEREMOTE: return "eremote";
#endif
#ifdef WSASYSNOTREADY
  case WSASYSNOTREADY: return "sysnotready";
#endif
#ifdef WSAVERNOTSUPPORTED
  case WSAVERNOTSUPPORTED: return "vernotsupported";
#endif
#ifdef WSANOTINITIALISED
  case WSANOTINITIALISED: return "notinitialised";
#endif
#ifdef WSAEDISCON
  case WSAEDISCON: return "ediscon";
#endif
#ifdef WSAENOMORE
  case WSAENOMORE: return "enomore";
#endif
#ifdef WSAECANCELLED
  case WSAECANCELLED: return "ecancelled";
#endif
#ifdef WSAEINVALIDPROCTABLE
  case WSAEINVALIDPROCTABLE: return "einvalidproctable";
#endif
#ifdef WSAEINVALIDPROVIDER
  case WSAEINVALIDPROVIDER: return "einvalidprovider";
#endif
#ifdef WSAEPROVIDERFAILEDINIT
    /* You could get this if SYSTEMROOT env variable is set incorrectly */
  case WSAEPROVIDERFAILEDINIT: return "eproviderfailedinit";
#endif
#ifdef WSASYSCALLFAILURE
  case WSASYSCALLFAILURE: return "syscallfailure";
#endif
#ifdef WSASERVICE_NOT_FOUND
  case WSASERVICE_NOT_FOUND: return "service_not_found";
#endif
#ifdef WSATYPE_NOT_FOUND
  case WSATYPE_NOT_FOUND: return "type_not_found";
#endif
#ifdef WSA_E_NO_MORE
  case WSA_E_NO_MORE: return "e_no_more";
#endif
#ifdef WSA_E_CANCELLED
  case WSA_E_CANCELLED: return "e_cancelled";
#endif
  }
  return "unknown";
}

ERL_NIF_TERM
mk_atom(ErlNifEnv* env, const char* atom)
{
  ERL_NIF_TERM ret;

  if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1))
    {
      return enif_make_atom(env, atom);
    }

  return ret;
}

ERL_NIF_TERM
mk_error(ErlNifEnv* env, const char* mesg)
{
  return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg));
}

static ERL_NIF_TERM
ebpf_attach_xdp(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int prog_fd = -1;
  int if_index = -1;
  int res = 0;

  if(argc != 2)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(env, argv[0], &if_index))
    {
      return mk_error(env, "bad_if_index");
    }

  if(!enif_get_int(env, argv[1], &prog_fd))
    {
      return mk_error(env, "bad_fd");
    }

  res = bpf_set_link_xdp_fd(if_index, prog_fd, 0);

  if(res < 0){
    return mk_error(env, erl_errno_id(errno));
  }
  
  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf__load_program_no_log(ErlNifEnv* env,
			  int type,
			  ErlNifBinary* bin,
			  const char * license,
			  uint32_t flags)
{
  int res = ebpf__prog_load(type,
			    (const struct bpf_insn *) bin->data,
			    bin->size / 8,
			    license,
			    NULL,
			    0,
			    0,
			    flags);
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }
  else
    {
      return enif_make_tuple2(env, mk_atom(env, "ok"), enif_make_int(env, res));
    }
}

static ERL_NIF_TERM
ebpf__load_program(ErlNifEnv* env,
		   int type,
		   ErlNifBinary * bin,
		   char * buf,
		   size_t buf_size,
		   const char * license,
		   uint32_t flags)
{
  int res = -1;
  int log_level = 1;

  if (buf_size == 0)
    {
      return ebpf__load_program_no_log(env, type, bin, license, flags);
    }

  res = ebpf__prog_load(type,
			(const struct bpf_insn *) bin->data,
			bin->size / 8,
			license,
			buf,
			buf_size,
			log_level,
			flags);
  if (res < 0)
    {
      switch (errno)
	{
	case EACCES:
	case EINVAL:
	case ENOSPC:
	case EBADF:
	  return enif_make_tuple3(env,
				  mk_atom(env, "error"),
				  mk_atom(env, erl_errno_id(errno)),
				  enif_make_string(env, buf, ERL_NIF_LATIN1));
	default:
	  return enif_make_tuple3(env,
				  mk_atom(env, "error"),
				  mk_atom(env, erl_errno_id(errno)),
				  enif_make_list(env, 0));

	}
    }
  else
    {
      return enif_make_tuple3(env, mk_atom(env, "ok"), enif_make_int(env, res), enif_make_string(env, buf, ERL_NIF_LATIN1));
    }
}

static ERL_NIF_TERM
ebpf_load_program5(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int type = 0;
  ErlNifBinary bin = {0,};
  char * buf = NULL;
  uint32_t buf_size = 0;
  uint32_t flags = 0;
  char license[256] = {0,};

  ERL_NIF_TERM res = {0,};

  if(argc != 5)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(env, argv[0], &type))
    {
      return enif_make_badarg(env);
    }


  if(!enif_inspect_binary(env, argv[1], &bin))
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_uint(env, argv[2], &buf_size))
    {
      return enif_make_badarg(env);
    }

  if(enif_get_string(env, argv[3], license, sizeof(license), ERL_NIF_LATIN1) <= 0)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_uint(env, argv[2], &buf_size))
    {
      return enif_make_badarg(env);
    }

  if(buf_size > 0)
    {
      buf = (char *) malloc (buf_size);

      if(buf == NULL)
	{
	  return mk_error(env, erl_errno_id(errno));
	}
      memset(buf, 0, buf_size);
    }
  res = ebpf__load_program(env, type, &bin, buf, buf_size, license, flags);
  if (buf)
    {
      free(buf);
    }

  return res;
}


static ERL_NIF_TERM
ebpf_attach_socket_filter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int prog_fd = 0;
  int sock_fd = 0;
  int res = -1;

  if(argc != 2)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(env, argv[0], &sock_fd))
    {
      return mk_error(env, "bad_sock_fd");
    }

  if(!enif_get_int(env, argv[1], &prog_fd))
    {
      return mk_error(env, "bad_prog_fd");
    }

  res = setsockopt(sock_fd, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd));
  if (res < 0) {
    return mk_error(env, erl_errno_id(errno));
  }

  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf_detach_socket_filter1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int sock_fd = 0, empty = 0;
  int res = -1;

  if(argc != 1)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(env, argv[0], &sock_fd))
    {
      return mk_error(env, "bad_sock_fd");
    }

  res = setsockopt(sock_fd, SOL_SOCKET, SO_DETACH_BPF, &empty, sizeof(empty));
  if (res < 0) {
    return mk_error(env, erl_errno_id(errno));
  }

  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf_create_map5(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int map_type = 0;
  int key_size = 0;
  int value_size = 0;
  int max_entries = 0;
  __u32 map_flags = 0;

  int res = -1;

  if(argc != 5)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int( env, argv[0], &map_type)
     || !enif_get_int( env, argv[1], &key_size)
     || !enif_get_int( env, argv[2], &value_size)
     || !enif_get_int( env, argv[3], &max_entries)
     || !enif_get_uint(env, argv[4], &map_flags))
    {
      return enif_make_badarg(env);
    }

  res = bpf_create_map(map_type, key_size, value_size, max_entries, map_flags);
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return enif_make_tuple2(env, mk_atom(env, "ok"), enif_make_int(env, res));
}

static ERL_NIF_TERM
ebpf_close1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd = -1;
  int res = -1;

  if (argc != 1)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int( env, argv[0], &fd))
    {
      return enif_make_badarg(env);
    }

  do {
    res = close(fd);
  } while (errno == EINTR);

  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf_update_map_element4(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd;
  ErlNifBinary key = {0,};
  ErlNifBinary value = {0,};
  __u64 flags = 0;

  int res = -1;

  if(argc != 4)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(       env, argv[0], &fd   )
     || !enif_inspect_binary(env, argv[1], &key  )
     || !enif_inspect_binary(env, argv[2], &value)
     || !enif_get_uint64(    env, argv[3], (unsigned long *)&flags))
    {
      return enif_make_badarg(env);
    }

  res = bpf_map_update_elem(fd, key.data, value.data, flags);
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf_lookup_map_element4(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd;
  ErlNifBinary key = {0,};
  ERL_NIF_TERM value = {0,};
  int value_size = 0;
  __u64 flags = 0;

  int res = -1;

  if(argc != 4)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(       env, argv[0], &fd   )
     || !enif_inspect_binary(env, argv[1], &key  )
     || !enif_get_int(       env, argv[2], &value_size)
     || !enif_get_uint64(    env, argv[3], (unsigned long *)&flags))
    {
      return enif_make_badarg(env);
    }

  res = bpf_map_lookup_elem_flags(fd, key.data, enif_make_new_binary(env, value_size, &value), flags);
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return enif_make_tuple2(env, mk_atom(env, "ok"), value);
}

static ERL_NIF_TERM
ebpf_delete_map_element2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd;
  ErlNifBinary key = {0,};

  int res = -1;

  if(argc != 2)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(       env, argv[0], &fd   )
     || !enif_inspect_binary(env, argv[1], &key  ))
    {
      return enif_make_badarg(env);
    }

  res = bpf_map_delete_elem(fd, key.data);
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return mk_atom(env, "ok");
}

static ERL_NIF_TERM
ebpf_get_map_next_key2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd = -1;
  ErlNifBinary key = {0,};
  ERL_NIF_TERM next_key = {0,};

  int res = -1;

  if(argc != 2)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(       env, argv[0], &fd   )
     || !enif_inspect_binary(env, argv[1], &key  ))
    {
      return enif_make_badarg(env);
    }

  res = bpf_map_get_next_key(fd, key.data, enif_make_new_binary(env, key.size, &next_key));
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return enif_make_tuple2(env, mk_atom(env, "ok"), next_key);
}



static ERL_NIF_TERM
ebpf_get_map_first_key2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int fd = -1;
  uint32_t key_size = 0;
  ERL_NIF_TERM next_key = {0,};

  int res = -1;

  if(argc != 2)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(env, argv[0], &fd)
  || !enif_get_uint(env, argv[1], &key_size))
    {
      return enif_make_badarg(env);
    }

  res = bpf_map_get_next_key(fd, NULL, enif_make_new_binary(env, key_size, &next_key));
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  return enif_make_tuple2(env, mk_atom(env, "ok"), next_key);
}




static ERL_NIF_TERM
ebpf_test_program4(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  int prog_fd = -1;
  int repeat = 0;
  ErlNifBinary data = {0,};
  void * data_out_ptr = NULL;
  __u32 data_out_size = 0;
  __u32 retval = 0;
  __u32 duration = 0;
  ERL_NIF_TERM data_out = {0,};

  int res = -1;

  if(argc != 4)
    {
      return enif_make_badarg(env);
    }

  if(!enif_get_int(       env, argv[0], &prog_fd)
     || !enif_get_int(       env, argv[1], &repeat)
     || !enif_inspect_binary(env, argv[2], &data   )
     || !enif_get_uint(      env, argv[3], &data_out_size))
    {
      return enif_make_badarg(env);
    }

  if(data_out_size > 0)
    {

      data_out_ptr = malloc(data_out_size);
      if(data_out_ptr == NULL)
	{
	  return mk_error(env, erl_errno_id(errno));
	}
      memset(data_out_ptr, 0, data_out_size);

      res = bpf_prog_test_run(prog_fd,
			      repeat,
			      data.data,
			      data.size,
			      data_out_ptr,
			      &data_out_size,
			      &retval,
			      &duration);
    }
  else
    {
      res = bpf_prog_test_run(prog_fd,
			      repeat,
			      data.data,
			      data.size,
			      NULL,
			      NULL,
			      &retval,
			      &duration);
    }
  if (res < 0)
    {
      return mk_error(env, erl_errno_id(errno));
    }

  memcpy(enif_make_new_binary(env, data_out_size, &data_out), data_out_ptr, data_out_size);

  return enif_make_tuple4(env, mk_atom(env, "ok"), enif_make_uint(env, retval), data_out, enif_make_uint(env, duration));
}

int ebpf_nif_lib_load(ErlNifEnv* caller_env, void** priv_data, ERL_NIF_TERM load_info)
{
  (void)caller_env;
  (void)priv_data;
  (void)load_info;
  return 0;
}

int ebpf_nif_lib_upgrade(ErlNifEnv* caller_env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
{
  (void)caller_env;
  (void)priv_data;
  (void)load_info;
  (void)old_priv_data;
  return 0;
}

static ErlNifFunc nif_funcs[] = {
				 {"bpf_load_program", 5, ebpf_load_program5, 0},
				 {"bpf_attach_socket_filter", 2, ebpf_attach_socket_filter, 0},
				 {"bpf_detach_socket_filter", 1, ebpf_detach_socket_filter1, 0},
				 {"bpf_attach_xdp", 2, ebpf_attach_xdp, 0},
				 {"bpf_create_map", 5, ebpf_create_map5, 0},
				 {"bpf_close", 1, ebpf_close1, 0},
				 {"bpf_update_map_element", 4, ebpf_update_map_element4, 0},
				 {"bpf_lookup_map_element", 4, ebpf_lookup_map_element4, 0},
				 {"bpf_delete_map_element", 2, ebpf_delete_map_element2, 0},
				 {"bpf_get_map_next_key", 2, ebpf_get_map_next_key2, 0},
				 {"bpf_get_map_first_key", 2, ebpf_get_map_first_key2, 0},
				 {"bpf_test_program", 4, ebpf_test_program4, 0}
};

ERL_NIF_INIT(ebpf_lib, nif_funcs, ebpf_nif_lib_load, NULL, ebpf_nif_lib_upgrade, NULL);
