using System;
using System.Runtime.InteropServices;

namespace nfapinet
{
    public enum NF_STATUS
    {
        NF_STATUS_SUCCESS = 0,
        NF_STATUS_FAIL = -1,
        NF_STATUS_INVALID_ENDPOINT_ID = -2,
        NF_STATUS_NOT_INITIALIZED = -3,
        NF_STATUS_IO_ERROR = -4
    };

    public enum NF_DIRECTION
    {
        NF_D_IN = 1,        // Incoming TCP connection or UDP packet
        NF_D_OUT = 2,       // Outgoing TCP connection or UDP packet
        NF_D_BOTH = 3       // Any direction
    };

    public enum NF_FILTERING_FLAG
    {
        NF_ALLOW = 0,       // Allow the activity without filtering transmitted packets
        NF_BLOCK = 1,       // Block the activity
        NF_FILTER = 2,      // Filter the transmitted packets
        NF_SUSPENDED = 4,   // Suspend receives from server and sends from client
        NF_OFFLINE = 8,     // Emulate establishing a TCP connection with remote server
        NF_INDICATE_CONNECT_REQUESTS = 16, // Indicate outgoing connect requests to API
        NF_DISABLE_REDIRECT_PROTECTION = 32, // Disable blocking indicating connect requests for outgoing connections of local proxies
        NF_PEND_CONNECT_REQUEST = 64, // Pend outgoing connect request to complete it later using nf_complete(TCP|UDP)ConnectRequest
        NF_FILTER_AS_IP_PACKETS = 128, // Indicate the traffic as IP packets via ipSend/ipReceive
        NF_READONLY = 256,              // Don't block the IP packets and indicate them to ipSend/ipReceive only for monitoring
        NF_CONTROL_FLOW = 512,          // Use the flow limit rules even without NF_FILTER flag
    };

    public enum NF_FLAGS
    {
        NFF_NONE = 0,
        NFF_DONT_DISABLE_TEREDO = 1, // Don't disable Teredo 
        NFF_DONT_DISABLE_TCP_OFFLOADING = 2 // Don't disable TCP offloading
    };


    public enum NF_CONSTS
    {
        NF_MAX_ADDRESS_LENGTH = 28,
        NF_MAX_IP_ADDRESS_LENGTH = 16
    };

    public enum NF_DRIVER_TYPE
    {
        DT_UNKNOWN = 0,
        DT_TDI = 1,
        DT_WFP = 2
    };

    /**
	*	Filtering rule
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_RULE
    {
        public int protocol;    // IPPROTO_TCP or IPPROTO_UDP        
        public UInt32 processId;    // Process identifier
        public Byte direction;  // See NF_DIRECTION
        public ushort localPort;    // Local port
        public ushort remotePort;   // Remote port
        public ushort ip_family;    // AF_INET for IPv4 and AF_INET6 for IPv6

        // Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddress;

        // Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddressMask;

        // Remote IP (or network if remoteIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddress;

        // Remote IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddressMask;

        public UInt32 filteringFlag;    // See NF_FILTERING_FLAG
    };

    /**
    *	Filtering rule with additional fields
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct NF_RULE_EX
    {
        public int protocol;    // IPPROTO_TCP or IPPROTO_UDP        
        public UInt32 processId;    // Process identifier
        public Byte direction;  // See NF_DIRECTION
        public ushort localPort;    // Local port
        public ushort remotePort;   // Remote port
        public ushort ip_family;    // AF_INET for IPv4 and AF_INET6 for IPv6

        // Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddress;

        // Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddressMask;

        // Remote IP (or network if remoteIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddress;

        // Remote IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] remoteIpAddressMask;

        public UInt32 filteringFlag;    // See NF_FILTERING_FLAG

        // Tail part of the process path mask 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string processName;
    };

    /**
	*	TCP connection properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_TCP_CONN_INFO
    {
        public UInt32 filteringFlag;    // See NF_FILTERING_FLAG
        public UInt32 processId;        // Process identifier
        public Byte direction;      // See NF_DIRECTION
        public ushort ip_family;        // AF_INET for IPv4 and AF_INET6 for IPv6

        // Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress;

        // Remote address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] remoteAddress;
    };

    /**
	*	UDP endpoint properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_CONN_INFO
    {
        public UInt32 processId;        // Process identifier
        public ushort ip_family;        // AF_INET for IPv4 and AF_INET6 for IPv6

        // Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress;
    };

    /**
    *	UDP options
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_OPTIONS
    {
        public UInt32 flags;        // UDP flags
        public Int32 optionsLength; // options length
        [MarshalAs(UnmanagedType.ByValArray)]
        public byte[] options; // Options array
    };

    /**
	*	UDP connect request properties
	**/
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_UDP_CONN_REQUEST
    {
        public UInt32 filteringFlag;    // See NF_FILTERING_FLAG
        public UInt32 processId;        // Process identifier
        public ushort ip_family;        // AF_INET for IPv4 and AF_INET6 for IPv6

        // Local address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] localAddress;

        // Remote address as sockaddr_in for IPv4 and sockaddr_in6 for IPv6
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_ADDRESS_LENGTH)]
        public byte[] remoteAddress;
    };

    public enum NF_IP_FLAG
    {
        NFIF_NONE = 0,      // No flags
        NFIF_READONLY = 1,  // The packet was not blocked and indicated only for monitoring in read-only mode 
                            // (see NF_READ_ONLY flags from NF_FILTERING_FLAG).
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_IP_PACKET_OPTIONS
    {
        public ushort ip_family;    // AF_INET for IPv4 and AF_INET6 for IPv6
        public UInt32 ipHeaderSize; // Size in bytes of IP header
        public UInt32 compartmentId;  // Network routing compartment identifier (can be zero)
        public UInt32 interfaceIndex; // Index of the interface on which the original packet data was received (irrelevant to outgoing packets)
        public UInt32 subInterfaceIndex;    // Index of the subinterface on which the original packet data was received (irrelevant to outgoing packets)
        public UInt32 flags;                // Can be a combination of flags from NF_IP_FLAG enumeration
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_DATA
    {
        public ulong inLimit;
        public ulong outLimit;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_MODIFY_DATA
    {
        public UInt32 fcHandle;
        public NF_FLOWCTL_DATA data;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_STAT
    {
        public ulong inBytes;
        public ulong outBytes;
    };

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct NF_FLOWCTL_SET_DATA
    {
        public ulong endpointId;
        public UInt32 fcHandle;
    };

    /**
    *	Binding rule
    **/
    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct NF_BINDING_RULE
    {
        // IPPROTO_TCP or IPPROTO_UDP
        public int protocol;

        // Process identifier
        public UInt32 processId;

        // Tail part of the process path mask 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string processName;

        // Local port
        public ushort localPort;

        // AF_INET for IPv4 and AF_INET6 for IPv6
        public ushort ip_family;

        // Local IP (or network if localIpAddressMask is not zero)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddress;

        // Local IP mask
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] localIpAddressMask;

        // Redirect bind request to this IP 
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)]
        public byte[] newLocalIpAddress;

        // Redirect bind request to this port, if it is not zero
        public ushort newLocalPort;

        // See NF_FILTERING_FLAG, NF_ALLOW or NF_FILTER
        public ulong filteringFlag;
    };

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_threadStart();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_threadEnd();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpConnected(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpClosed(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpReceive(ulong id, IntPtr buf, int len);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpSend(ulong id, IntPtr buf, int len);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpCanReceive(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_tcpCanSend(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCreated(ulong id, ref NF_UDP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpClosed(ulong id, ref NF_UDP_CONN_INFO pConnInfo);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCanReceive(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_udpCanSend(ulong id);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void cbd_ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);

    /*
     * Event handler interface
     */
    public interface NF_EventHandler
    {
        void threadStart();
        void threadEnd();
        void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);
        void tcpConnected(ulong id, NF_TCP_CONN_INFO pConnInfo);
        void tcpClosed(ulong id, NF_TCP_CONN_INFO pConnInfo);
        void tcpReceive(ulong id, IntPtr buf, int len);
        void tcpSend(ulong id, IntPtr buf, int len);
        void tcpCanReceive(ulong id);
        void tcpCanSend(ulong id);
        void udpCreated(ulong id, NF_UDP_CONN_INFO pConnInfo);
        void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq);
        void udpClosed(ulong id, NF_UDP_CONN_INFO pConnInfo);
        void udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options, int optionsLen);
        void udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options, int optionsLen);
        void udpCanReceive(ulong id);
        void udpCanSend(ulong id);
    };

    /*
     * IP event handler interface
     */
    public interface NF_IPEventHandler
    {
        void ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
        void ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions);
    };

    /*
     *  Internal events forwarder
     */
    class NF_EventHandlerFwd
    {
        public static NF_EventHandler m_pEventHandler = null;

        public static void threadStart()
        {
            m_pEventHandler.threadStart();
        }
        public static void threadEnd()
        {
            m_pEventHandler.threadEnd();
        }
        public static void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpConnectRequest(id, ref pConnInfo);
        }
        public static void tcpConnected(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpConnected(id, pConnInfo);
        }
        public static void tcpClosed(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.tcpClosed(id, pConnInfo);
        }
        public static void tcpReceive(ulong id, IntPtr buf, int len)
        {
            m_pEventHandler.tcpReceive(id, buf, len);
        }
        public static void tcpSend(ulong id, IntPtr buf, int len)
        {
            m_pEventHandler.tcpSend(id, buf, len);
        }
        public static void tcpCanReceive(ulong id)
        {
            m_pEventHandler.tcpCanReceive(id);
        }
        public static void tcpCanSend(ulong id)
        {
            m_pEventHandler.tcpCanSend(id);
        }
        public static void udpCreated(ulong id, ref NF_UDP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.udpCreated(id, pConnInfo);
        }
        public static void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq)
        {
            m_pEventHandler.udpConnectRequest(id, ref pConnReq);
        }
        public static void udpClosed(ulong id, ref NF_UDP_CONN_INFO pConnInfo)
        {
            m_pEventHandler.udpClosed(id, pConnInfo);
        }
        public static void udpReceive(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options)
        {
            if (options.ToInt64() != 0)
            {
                NF_UDP_OPTIONS optionsCopy = (NF_UDP_OPTIONS)Marshal.PtrToStructure((IntPtr)options, typeof(NF_UDP_OPTIONS));
                int optionsLen = 8 + optionsCopy.optionsLength;
                m_pEventHandler.udpReceive(id, remoteAddress, buf, len, options, optionsLen);
            }
            else
            {
                m_pEventHandler.udpReceive(id, remoteAddress, buf, len, (IntPtr)null, 0);
            }
        }
        public static void udpSend(ulong id, IntPtr remoteAddress, IntPtr buf, int len, IntPtr options)
        {
            if (options.ToInt64() != 0)
            {
                NF_UDP_OPTIONS optionsCopy = (NF_UDP_OPTIONS)Marshal.PtrToStructure((IntPtr)options, typeof(NF_UDP_OPTIONS));
                int optionsLen = 8 + optionsCopy.optionsLength;
                m_pEventHandler.udpSend(id, remoteAddress, buf, len, options, optionsLen);
            }
            else
            {
                m_pEventHandler.udpSend(id, remoteAddress, buf, len, (IntPtr)null, 0);
            }
        }
        public static void udpCanReceive(ulong id)
        {
            m_pEventHandler.udpCanReceive(id);
        }
        public static void udpCanSend(ulong id)
        {
            m_pEventHandler.udpCanSend(id);
        }
    };

    /*
     *  Internal IP events forwarder
     */
    class NF_IPEventHandlerFwd
    {
        public static NF_IPEventHandler m_pEventHandler = null;

        public static void ipReceive(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions)
        {
            m_pEventHandler.ipReceive(buf, len, ref ipOptions);
        }
        public static void ipSend(IntPtr buf, int len, ref NF_IP_PACKET_OPTIONS ipOptions)
        {
            m_pEventHandler.ipSend(buf, len, ref ipOptions);
        }
    };

    /*
     *  Event handler structure for C API
     */
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct NF_EventHandlerInternal
    {
        public cbd_threadStart threadStart;
        public cbd_threadEnd threadEnd;
        public cbd_tcpConnectRequest tcpConnectRequest;
        public cbd_tcpConnected tcpConnected;
        public cbd_tcpClosed tcpClosed;
        public cbd_tcpReceive tcpReceive;
        public cbd_tcpSend tcpSend;
        public cbd_tcpCanReceive tcpCanReceive;
        public cbd_tcpCanSend tcpCanSend;
        public cbd_udpCreated udpCreated;
        public cbd_udpConnectRequest udpConnectRequest;
        public cbd_udpClosed udpClosed;
        public cbd_udpReceive udpReceive;
        public cbd_udpSend udpSend;
        public cbd_udpCanReceive udpCanReceive;
        public cbd_udpCanSend udpCanSend;
    };

    /*
     *  IP event handler structure for C API
     */
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct NF_IPEventHandlerInternal
    {
        public cbd_ipReceive ipReceive;
        public cbd_ipSend ipSend;
    };

    // Managed wrapper over API 
    public class NFAPI
    {
        private static IntPtr m_pEventHandlerRaw = (IntPtr)null;
        private static NF_EventHandlerInternal m_pEventHandler;
        private static IntPtr m_pIPEventHandlerRaw = (IntPtr)null;
        private static NF_IPEventHandlerInternal m_pIPEventHandler;

        /**
		* Initializes the internal data structures and starts the filtering thread.
		* @param driverName The name of hooking driver, without ".sys" extension.
		* @param pHandler Pointer to event handling object
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_init(String driverName, IntPtr pHandler);

        public static NF_STATUS nf_init(String driverName, NF_EventHandler pHandler)
        {
            NF_EventHandlerFwd.m_pEventHandler = pHandler;

            nf_adjustProcessPriviledges();

            m_pEventHandler = new NF_EventHandlerInternal();

            m_pEventHandler.threadStart = new cbd_threadStart(NF_EventHandlerFwd.threadStart);
            m_pEventHandler.threadEnd = new cbd_threadEnd(NF_EventHandlerFwd.threadEnd);
            m_pEventHandler.tcpConnectRequest = new cbd_tcpConnectRequest(NF_EventHandlerFwd.tcpConnectRequest);
            m_pEventHandler.tcpConnected = new cbd_tcpConnected(NF_EventHandlerFwd.tcpConnected);
            m_pEventHandler.tcpClosed = new cbd_tcpClosed(NF_EventHandlerFwd.tcpClosed);
            m_pEventHandler.tcpReceive = new cbd_tcpReceive(NF_EventHandlerFwd.tcpReceive);
            m_pEventHandler.tcpSend = new cbd_tcpSend(NF_EventHandlerFwd.tcpSend);
            m_pEventHandler.tcpCanReceive = new cbd_tcpCanReceive(NF_EventHandlerFwd.tcpCanReceive);
            m_pEventHandler.tcpCanSend = new cbd_tcpCanSend(NF_EventHandlerFwd.tcpCanSend);
            m_pEventHandler.udpCreated = new cbd_udpCreated(NF_EventHandlerFwd.udpCreated);
            m_pEventHandler.udpConnectRequest = new cbd_udpConnectRequest(NF_EventHandlerFwd.udpConnectRequest);
            m_pEventHandler.udpClosed = new cbd_udpClosed(NF_EventHandlerFwd.udpClosed);
            m_pEventHandler.udpReceive = new cbd_udpReceive(NF_EventHandlerFwd.udpReceive);
            m_pEventHandler.udpSend = new cbd_udpSend(NF_EventHandlerFwd.udpSend);
            m_pEventHandler.udpCanReceive = new cbd_udpCanReceive(NF_EventHandlerFwd.udpCanReceive);
            m_pEventHandler.udpCanSend = new cbd_udpCanSend(NF_EventHandlerFwd.udpCanSend);

            m_pEventHandlerRaw = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_EventHandlerInternal)));
            Marshal.StructureToPtr(m_pEventHandler, m_pEventHandlerRaw, true);

            return nf_init(driverName, m_pEventHandlerRaw);
        }

        /**
		* Stops the filtering thread, breaks all filtered connections and closes
		* a connection with the hooking driver.
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_free();

        /**
		* Registers and starts a driver with specified name (without ".sys" extension)
		* @param driverName 
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_registerDriver(String driverName);

        /**
		* Unregisters a driver with specified name (without ".sys" extension)
		* @param driverName 
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_unRegisterDriver(String driverName);


        //
        // TCP control routines
        //

        /**
		* Suspends or resumes indicating of sends and receives for specified connection.
		* @param id Connection identifier
		* @param suspended true for suspend, false for resume 
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetConnectionState(ulong id, int suspended);

        /**
		* Sends the buffer to remote server via specified connection.
		* @param id Connection identifier
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpPostSend(ulong id, IntPtr buf, int len);

        /**
		* Indicates the buffer to local process via specified connection.
		* @param id Unique connection identifier
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpPostReceive(ulong id, IntPtr buf, int len);

        /**
		* Breaks the connection with given id.
		* @param id Connection identifier
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpClose(ulong id);

        //
        // UDP control routines
        //

        /**
		* Suspends or resumes indicating of sends and receives for specified socket.
		* @param id Socket identifier
		* @param suspended true for suspend, false for resume 
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpSetConnectionState(ulong id, int suspended);

        /**
		* Sends the buffer to remote server via specified socket.
		* @param id Socket identifier
		* @param remoteAddress Destination address
		* @param buf Pointer to data buffer
		* @param len Buffer length
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpPostSend(ulong id,
            IntPtr remoteAddress,
            IntPtr buf, int len,
            IntPtr options);

        /**
        * Indicates the buffer to local process via specified socket.
        * @param id Unique connection identifier
        * @param remoteAddress Source address
        * @param buf Pointer to data buffer
        * @param len Buffer length
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpPostReceive(ulong id,
            IntPtr remoteAddress,
            IntPtr buf, int len,
            IntPtr options);

        /**
		* Indicates a packet to TCP/IP stack
		* @param buf Pointer to IP packet
		* @param len Buffer length
		* @param options IP options
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_ipPostReceive(
            IntPtr buf, int len,
            ref NF_IP_PACKET_OPTIONS options);

        /**
		* Sends a packet to remote IP
		* @param buf Pointer to IP packet
		* @param len Buffer length
		* @param options IP options
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_ipPostSend(
            IntPtr buf, int len,
            ref NF_IP_PACKET_OPTIONS options);

        //
        // Filtering rules 
        //

        /**
		* Add a rule to the head of rules list in driver.
		* @param pRule See <tt>NF_RULE</tt>
		* @param toHead TRUE (1) - add rule to list head, FALSE (0) - add rule to tail
		**/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_addRule(ref NF_RULE pRule, int toHead);

        private static void updateAddressLength(ref byte[] buf)
        {
            if (buf == null)
            {
                buf = new byte[(int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH];
            }
            else
            {
                if (buf.Length < (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH)
                {
                    Array.Resize(ref buf, (int)NF_CONSTS.NF_MAX_IP_ADDRESS_LENGTH);
                }
            }
        }

        public static NF_STATUS nf_addRule(NF_RULE pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.remoteIpAddress);
            updateAddressLength(ref pRule.remoteIpAddressMask);

            return nf_addRule(ref pRule, toHead);
        }

        /**
        * Add a rule to the head of rules list in driver.
        * @param pRule See <tt>NF_RULE</tt>
        * @param toHead TRUE (1) - add rule to list head, FALSE (0) - add rule to tail
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_addRuleEx(ref NF_RULE_EX pRule, int toHead);

        public static NF_STATUS nf_addRuleEx(NF_RULE_EX pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.remoteIpAddress);
            updateAddressLength(ref pRule.remoteIpAddressMask);

            return nf_addRuleEx(ref pRule, toHead);
        }

        /**
        * Replace the rules in driver with the specified array.
        * @param pRules Array of <tt>NF_RULE</tt> structures
        * @param count Number of items in array
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_setRules(IntPtr pRules, int count);

        public static NF_STATUS nf_setRules(NF_RULE[] rules)
        {
            NF_RULE pRule;

            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_RULE)) * rules.Length);

            long longPtr = ptr.ToInt64();
            for (int i = 0; i < rules.Length; i++)
            {
                pRule = rules[i];

                updateAddressLength(ref pRule.localIpAddress);
                updateAddressLength(ref pRule.localIpAddressMask);
                updateAddressLength(ref pRule.remoteIpAddress);
                updateAddressLength(ref pRule.remoteIpAddressMask);

                Marshal.StructureToPtr(pRule, new IntPtr(longPtr), false);

                longPtr += Marshal.SizeOf(typeof(NF_RULE));
            }

            return nf_setRules(ptr, rules.Length);
        }

        /**
        * Replace the rules in driver with the specified array.
        * @param pRules Array of <tt>NF_RULE</tt> structures
        * @param count Number of items in array
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS nf_setRulesEx(IntPtr pRules, int count);

        public static NF_STATUS nf_setRulesEx(NF_RULE_EX[] rules)
        {
            NF_RULE_EX pRule;

            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_RULE_EX)) * rules.Length);

            long longPtr = ptr.ToInt64();
            for (int i = 0; i < rules.Length; i++)
            {
                pRule = rules[i];

                updateAddressLength(ref pRule.localIpAddress);
                updateAddressLength(ref pRule.localIpAddressMask);
                updateAddressLength(ref pRule.remoteIpAddress);
                updateAddressLength(ref pRule.remoteIpAddressMask);

                Marshal.StructureToPtr(pRule, new IntPtr(longPtr), false);

                longPtr += Marshal.SizeOf(typeof(NF_RULE_EX));
            }

            return nf_setRules(ptr, rules.Length);
        }

        /**
        * Removes all rules from driver.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_deleteRules();

        /**
		 *	Sets the timeout for TCP connections and returns old timeout.
		 *	@param timeout Timeout value in milliseconds. Specify zero value to disable timeouts.
		 */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern UInt32 nf_setTCPTimeout(UInt32 timeout);

        /**
		 *	Disables indicating TCP packets to user mode for the specified endpoint
		 *  @param id Socket identifier
		 */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpDisableFiltering(ulong id);

        /**
		 *	Disables indicating UDP packets to user mode for the specified endpoint
		 *  @param id Socket identifier
		 */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_udpDisableFiltering(ulong id);

        /**
        * Returns TRUE if the specified process acts as a local proxy, accepting the redirected TCP connections.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_tcpIsProxy(uint processId);

        /**
        * Set the number of worker threads and initialization flags.
        * The function should be called before nf_init. 
        * By default nThreads = 1 and flags = 0
        * @param nThreads Number of worker threads for NF_EventHandler events 
        * @param flags A combination of flags from <tt>NF_FLAGS</tt>
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_setOptions(uint nThreads, uint flags);

        /**
        * Complete TCP connect request pended using flag NF_PEND_CONNECT_REQUEST.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_completeTCPConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo);

        /**
        * Complete UDP connect request pended using flag NF_PEND_CONNECT_REQUEST.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_completeUDPConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnInfo);

        /**
        * Returns in pConnInfo the properties of TCP connection with specified id.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getTCPConnInfo(ulong id, ref NF_TCP_CONN_INFO pConnInfo);

        /**
        * Returns in pConnInfo the properties of UDP socket with specified id.
        **/
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getUDPConnInfo(ulong id, ref NF_UDP_CONN_INFO pConnInfo);

        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_getProcessNameW(uint processId, IntPtr buf, int len);

        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool nf_getProcessNameFromKernel(uint processId, IntPtr buf, int len);

        /**
        * Returns the process name for given process id
        **/
        public static unsafe String nf_getProcessNameFromKernel(UInt32 processId)
        {
            char[] buf = new char[1024];

            fixed (char* p = buf)
            {
                if (nf_getProcessNameFromKernel(processId, (IntPtr)p, buf.Length))
                {
                    return Marshal.PtrToStringUni((IntPtr)p);
                }
            }

            return "System";
        }

        /**
        * Returns the process name for given process id
        **/
        public static unsafe String nf_getProcessName(UInt32 processId)
        {
            char[] buf = new char[256];

            fixed (char* p = buf)
            {
                if (nf_getProcessNameW(processId, (IntPtr)p, buf.Length))
                {
                    return Marshal.PtrToStringUni((IntPtr)p);
                }
            }

            return "System";
        }

        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern void nf_adjustProcessPriviledges();

        /*
        * Set the event handler for IP filtering events
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern void nf_setIPEventHandler(IntPtr pHandler);

        /*
        * Set the event handler for IP filtering events
        */
        public static void nf_setIPEventHandler(NF_IPEventHandler pHandler)
        {
            NF_IPEventHandlerFwd.m_pEventHandler = pHandler;

            m_pIPEventHandler = new NF_IPEventHandlerInternal();

            m_pIPEventHandler.ipReceive = new cbd_ipReceive(NF_IPEventHandlerFwd.ipReceive);
            m_pIPEventHandler.ipSend = new cbd_ipSend(NF_IPEventHandlerFwd.ipSend);

            m_pIPEventHandlerRaw = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NF_IPEventHandlerInternal)));
            Marshal.StructureToPtr(m_pIPEventHandler, m_pIPEventHandlerRaw, true);

            nf_setIPEventHandler(m_pIPEventHandlerRaw);
        }

        /**
        * Add flow control context
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_addFlowCtl(ref NF_FLOWCTL_DATA pData, ref UInt32 pFcHandle);

        /**
        * Delete flow control context
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_deleteFlowCtl(UInt32 fcHandle);

        /**
        * Associate flow control context with TCP connection
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_setTCPFlowCtl(ulong id, UInt32 fcHandle);

        /**
        * Associate flow control context with UDP socket
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_setUDPFlowCtl(ulong id, UInt32 fcHandle);

        /**
        * Modify flow control context limits
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_modifyFlowCtl(UInt32 fcHandle, ref NF_FLOWCTL_DATA pData);

        /**
        * Get flow control context statistics as the numbers of in/out bytes
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getFlowCtlStat(UInt32 fcHandle, ref NF_FLOWCTL_STAT pStat);

        /**
        * Get TCP connection statistics as the numbers of in/out bytes.
        * The function can be called only from tcpClosed handler!
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getTCPStat(ulong id, ref NF_FLOWCTL_STAT pStat);

        /**
        * Get UDP socket statistics as the numbers of in/out bytes.
        * The function can be called only from udpClosed handler!
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_getUDPStat(ulong id, ref NF_FLOWCTL_STAT pStat);

        public enum NF_SOCKET_OPTIONS
        {
            TCP_SOCKET_NODELAY = 1,
            TCP_SOCKET_KEEPALIVE = 2,
            TCP_SOCKET_OOBINLINE = 3,
            TCP_SOCKET_BSDURGENT = 4,
            TCP_SOCKET_ATMARK = 5,
            TCP_SOCKET_WINDOW = 6
        }

        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, ref int optval, int optlen);

        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, IntPtr optval, int optlen);

        public static NF_STATUS nf_tcpSetSockOpt(ulong id, NF_SOCKET_OPTIONS optname, bool optval)
        {
            int dword = optval ? 1 : 0;
            return nf_tcpSetSockOpt(id, optname, ref dword, Marshal.SizeOf(typeof(int)));
        }

        /**
        * Add binding rule to driver
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        private static extern NF_STATUS
        nf_addBindingRule(ref NF_BINDING_RULE pRule, int toHead);

        public static NF_STATUS
        nf_addBindingRule(NF_BINDING_RULE pRule, int toHead)
        {
            updateAddressLength(ref pRule.localIpAddress);
            updateAddressLength(ref pRule.localIpAddressMask);
            updateAddressLength(ref pRule.newLocalIpAddress);

            return nf_addBindingRule(ref pRule, toHead);
        }

        /**
        * Delete all binding rules from driver
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern NF_STATUS
        nf_deleteBindingRules();

        /**
        * Returns the type of attached driver (DT_WFP, DT_TDI or DT_UNKNOWN)
        */
        [DllImport("bin\\nfapinet", CallingConvention = CallingConvention.Cdecl)]
        public static extern UInt32
        nf_getDriverType();
    };

}
