/*
 *  TAP-Windows -- A kernel driver to provide virtual tap
 *                 device functionality on Windows.
 *
 *  This code was inspired by the CIPE-Win32 driver by Damion K. Wilson.
 *
 *  This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc.,
 *  and is released under the GPL version 2 (see below).
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  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 (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

//
// Include files.
//

#include <wdmsec.h>  // for SDDLs
#include "tap.h"

//======================================================================
// TAP Win32 Device I/O Callbacks
//======================================================================

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, TapDeviceCreate)
#pragma alloc_text(PAGE, TapDeviceControl)
#pragma alloc_text(PAGE, TapDeviceCleanup)
#pragma alloc_text(PAGE, TapDeviceClose)
#endif  // ALLOC_PRAGMA

//===================================================================
// Go back to default TAP mode from Point-To-Point mode.
// Also reset (i.e. disable) DHCP Masq mode.
//===================================================================
VOID tapResetAdapterState(__in PTAP_ADAPTER_CONTEXT Adapter) {
  // Point-To-Point
  Adapter->m_tun = FALSE;
  Adapter->m_localIP = 0;
  Adapter->m_remoteNetwork = 0;
  Adapter->m_remoteNetmask = 0;
  NdisZeroMemory(&Adapter->m_TapToUser, sizeof(Adapter->m_TapToUser));
  NdisZeroMemory(&Adapter->m_UserToTap, sizeof(Adapter->m_UserToTap));
  NdisZeroMemory(&Adapter->m_UserToTap_IPv6, sizeof(Adapter->m_UserToTap_IPv6));

  // DHCP Masq
  Adapter->m_dhcp_enabled = FALSE;
  Adapter->m_dhcp_server_arp = FALSE;
  Adapter->m_dhcp_user_supplied_options_buffer_len = 0;
  Adapter->m_dhcp_addr = 0;
  Adapter->m_dhcp_netmask = 0;
  Adapter->m_dhcp_server_ip = 0;
  Adapter->m_dhcp_lease_time = 0;
  Adapter->m_dhcp_received_discover = FALSE;
  Adapter->m_dhcp_bad_requests = 0;
  NdisZeroMemory(Adapter->m_dhcp_server_mac, MACADDR_SIZE);
}

// IRP_MJ_CREATE
NTSTATUS
TapDeviceCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
/*++

Routine Description:

    This routine is called by the I/O system when the device is opened.

    No action is performed other than completing the request successfully.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/
{
  NDIS_STATUS status;
  PIO_STACK_LOCATION irpSp;  // Pointer to current stack location
  PTAP_ADAPTER_CONTEXT adapter = NULL;
  PFILE_OBJECT originalFileObject;

  PAGED_CODE();

  DEBUGP(("[TAP] --> TapDeviceCreate\n"));

  irpSp = IoGetCurrentIrpStackLocation(Irp);

  //
  // Invalidate file context
  //
  irpSp->FileObject->FsContext = NULL;
  irpSp->FileObject->FsContext2 = NULL;

  //
  // Find adapter context for this device.
  // -------------------------------------
  // Returns with added reference on adapter context.
  //
  adapter = tapAdapterContextFromDeviceObject(DeviceObject);

  // Insure that adapter exists.
  ASSERT(adapter);

  if (adapter == NULL) {
    DEBUGP(("[TAP] release [%d.%d] open request; adapter not found\n", TAP_DRIVER_MAJOR_VERSION,
            TAP_DRIVER_MINOR_VERSION));

    Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
    Irp->IoStatus.Information = 0;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_DEVICE_DOES_NOT_EXIST;
  }

  DEBUGP(("[%s] [TAP] release [%d.%d] open request (TapFileIsOpen=%d)\n",
          MINIPORT_INSTANCE_ID(adapter), TAP_DRIVER_MAJOR_VERSION, TAP_DRIVER_MINOR_VERSION,
          adapter->TapFileIsOpen));

  // Enforce exclusive access
  originalFileObject =
      InterlockedCompareExchangePointer(&adapter->TapFileObject, irpSp->FileObject, NULL);

  if (originalFileObject == NULL) {
    irpSp->FileObject->FsContext = adapter;  // Quick reference

    status = STATUS_SUCCESS;
  } else {
    status = STATUS_UNSUCCESSFUL;
  }

  // Release the lock.
  // tapAdapterReleaseLock(adapter,FALSE);

  if (status == STATUS_SUCCESS) {
    // Reset adapter state on successful open.
    tapResetAdapterState(adapter);

    adapter->TapFileIsOpen = 1;  // Legacy...

    // NOTE!!! Reference added by tapAdapterContextFromDeviceObject
    // will be removed when file is closed.
  } else {
    DEBUGP(("[%s] TAP is presently unavailable (TapFileIsOpen=%d)\n", MINIPORT_INSTANCE_ID(adapter),
            adapter->TapFileIsOpen));

    NOTE_ERROR();

    // Remove reference added by tapAdapterContextFromDeviceObject.
    tapAdapterContextDereference(adapter);
  }

  // Complete the IRP.
  Irp->IoStatus.Status = status;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  DEBUGP(("[TAP] <-- TapDeviceCreate; status = %8.8X\n", status));

  return status;
}

//===================================================
// Tell Windows whether the TAP device should be
// considered "connected" or "disconnected".
//
// Allows application control of media connect state.
//===================================================
VOID tapSetMediaConnectStatus(__in PTAP_ADAPTER_CONTEXT Adapter, __in BOOLEAN LogicalMediaState) {
  NDIS_STATUS_INDICATION statusIndication;
  NDIS_LINK_STATE linkState;

  NdisZeroMemory(&statusIndication, sizeof(NDIS_STATUS_INDICATION));
  NdisZeroMemory(&linkState, sizeof(NDIS_LINK_STATE));

  //
  // Fill in object headers
  //
  statusIndication.Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION;
  statusIndication.Header.Revision = NDIS_STATUS_INDICATION_REVISION_1;
  statusIndication.Header.Size = sizeof(NDIS_STATUS_INDICATION);

  linkState.Header.Revision = NDIS_LINK_STATE_REVISION_1;
  linkState.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
  linkState.Header.Size = sizeof(NDIS_LINK_STATE);

  //
  // Link state buffer
  //
  if (Adapter->LogicalMediaState == TRUE) {
    linkState.MediaConnectState = MediaConnectStateConnected;
  }

  linkState.MediaDuplexState = MediaDuplexStateFull;
  linkState.RcvLinkSpeed = TAP_RECV_SPEED;
  linkState.XmitLinkSpeed = TAP_XMIT_SPEED;

  //
  // Fill in the status buffer
  //
  statusIndication.StatusCode = NDIS_STATUS_LINK_STATE;
  statusIndication.SourceHandle = Adapter->MiniportAdapterHandle;
  statusIndication.DestinationHandle = NULL;
  statusIndication.RequestId = 0;

  statusIndication.StatusBuffer = &linkState;
  statusIndication.StatusBufferSize = sizeof(NDIS_LINK_STATE);

  // Fill in new media connect state.
  if ((Adapter->LogicalMediaState != LogicalMediaState) && !Adapter->MediaStateAlwaysConnected) {
    Adapter->LogicalMediaState = LogicalMediaState;

    if (LogicalMediaState == TRUE) {
      linkState.MediaConnectState = MediaConnectStateConnected;

      DEBUGP(("[TAP] Set MediaConnectState: Connected.\n"));
    } else {
      linkState.MediaConnectState = MediaConnectStateDisconnected;

      DEBUGP(("[TAP] Set MediaConnectState: Disconnected.\n"));
    }
  }

  // Make the status indication.
  if (Adapter->Locked.AdapterState != MiniportHaltedState) {
    NdisMIndicateStatusEx(Adapter->MiniportAdapterHandle, &statusIndication);
  }
}

//======================================================
// If DHCP mode is used together with tun
// mode, consider the fact that the P2P remote subnet
// might enclose the DHCP masq server address.
//======================================================
VOID CheckIfDhcpAndTunMode(__in PTAP_ADAPTER_CONTEXT Adapter) {
  if (Adapter->m_tun && Adapter->m_dhcp_enabled) {
    if ((Adapter->m_dhcp_server_ip & Adapter->m_remoteNetmask) == Adapter->m_remoteNetwork) {
      ETH_COPY_NETWORK_ADDRESS(Adapter->m_dhcp_server_mac, Adapter->m_TapToUser.dest);
      Adapter->m_dhcp_server_arp = FALSE;
    }
  }
}

// IRP_MJ_DEVICE_CONTROL callback.
NTSTATUS
TapDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)

/*++

Routine Description:

    This routine is called by the I/O system to perform a device I/O
    control function.

Arguments:

    DeviceObject - a pointer to the object that represents the device
        that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/

{
  NTSTATUS ntStatus = STATUS_SUCCESS;  // Assume success
  PIO_STACK_LOCATION irpSp;            // Pointer to current stack location
  PTAP_ADAPTER_CONTEXT adapter = NULL;
  ULONG inBufLength;    // Input buffer length
  ULONG outBufLength;   // Output buffer length
  PCHAR inBuf, outBuf;  // pointer to Input and output buffer
  PMDL mdl = NULL;
  PCHAR buffer = NULL;

  PAGED_CODE();

  irpSp = IoGetCurrentIrpStackLocation(Irp);

  //
  // Fetch adapter context for this device.
  // --------------------------------------
  // Adapter pointer was stashed in FsContext when handle was opened.
  //
  adapter = (PTAP_ADAPTER_CONTEXT)(irpSp->FileObject)->FsContext;

  ASSERT(adapter);

  inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
  outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

  if (!inBufLength || !outBufLength) {
    ntStatus = STATUS_INVALID_PARAMETER;
    goto End;
  }

  //
  // Determine which I/O control code was specified.
  //
  switch (irpSp->Parameters.DeviceIoControl.IoControlCode) {
    case TAP_WIN_IOCTL_GET_MAC: {
      if (outBufLength >= MACADDR_SIZE) {
        ETH_COPY_NETWORK_ADDRESS(Irp->AssociatedIrp.SystemBuffer, adapter->CurrentAddress);

        Irp->IoStatus.Information = MACADDR_SIZE;
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL;
      }
    } break;

    case TAP_WIN_IOCTL_GET_VERSION: {
      const ULONG size = sizeof(ULONG) * 3;

      if (outBufLength >= size) {
        ((PULONG)(Irp->AssociatedIrp.SystemBuffer))[0] = TAP_DRIVER_MAJOR_VERSION;

        ((PULONG)(Irp->AssociatedIrp.SystemBuffer))[1] = TAP_DRIVER_MINOR_VERSION;

        ((PULONG)(Irp->AssociatedIrp.SystemBuffer))[2]
#if DBG
            = 1;
#else
            = 0;
#endif
        Irp->IoStatus.Information = size;
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL;
      }
    } break;

    case TAP_WIN_IOCTL_GET_MTU: {
      const ULONG size = sizeof(ULONG) * 1;

      if (outBufLength >= size) {
        ((PULONG)(Irp->AssociatedIrp.SystemBuffer))[0] = adapter->MtuSize;

        Irp->IoStatus.Information = size;
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL;
      }
    } break;

    case TAP_WIN_IOCTL_CONFIG_TUN: {
      if (inBufLength >= sizeof(IPADDR) * 3) {
        MACADDR dest;

        adapter->m_tun = FALSE;

        GenerateRelatedMAC(dest, adapter->CurrentAddress, 1);

        adapter->m_localIP = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[0];
        adapter->m_remoteNetwork = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[1];
        adapter->m_remoteNetmask = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[2];

        // Sanity check on network/netmask
        if ((adapter->m_remoteNetwork & adapter->m_remoteNetmask) != adapter->m_remoteNetwork) {
          NOTE_ERROR();
          Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
          break;
        }

        ETH_COPY_NETWORK_ADDRESS(adapter->m_TapToUser.src, adapter->CurrentAddress);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_TapToUser.dest, dest);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_UserToTap.src, dest);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_UserToTap.dest, adapter->CurrentAddress);

        adapter->m_TapToUser.proto = adapter->m_UserToTap.proto = htons(NDIS_ETH_TYPE_IPV4);
        adapter->m_UserToTap_IPv6 = adapter->m_UserToTap;
        adapter->m_UserToTap_IPv6.proto = htons(NDIS_ETH_TYPE_IPV6);

        adapter->m_tun = TRUE;

        CheckIfDhcpAndTunMode(adapter);

        Irp->IoStatus.Information = 1;  // Simple boolean value

        DEBUGP(("[TAP] Set TUN mode.\n"));
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
      }
    } break;

    case TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT: {
      if (inBufLength >= sizeof(IPADDR) * 2) {
        MACADDR dest;

        adapter->m_tun = FALSE;

        GenerateRelatedMAC(dest, adapter->CurrentAddress, 1);

        adapter->m_localIP = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[0];
        adapter->m_remoteNetwork = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[1];
        adapter->m_remoteNetmask = ~0;

        ETH_COPY_NETWORK_ADDRESS(adapter->m_TapToUser.src, adapter->CurrentAddress);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_TapToUser.dest, dest);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_UserToTap.src, dest);
        ETH_COPY_NETWORK_ADDRESS(adapter->m_UserToTap.dest, adapter->CurrentAddress);

        adapter->m_TapToUser.proto = adapter->m_UserToTap.proto = htons(NDIS_ETH_TYPE_IPV4);
        adapter->m_UserToTap_IPv6 = adapter->m_UserToTap;
        adapter->m_UserToTap_IPv6.proto = htons(NDIS_ETH_TYPE_IPV6);

        adapter->m_tun = TRUE;

        CheckIfDhcpAndTunMode(adapter);

        Irp->IoStatus.Information = 1;  // Simple boolean value

        DEBUGP(("[TAP] Set P2P mode.\n"));
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
      }
    } break;

    case TAP_WIN_IOCTL_CONFIG_DHCP_MASQ: {
      if (inBufLength >= sizeof(IPADDR) * 4) {
        adapter->m_dhcp_enabled = FALSE;
        adapter->m_dhcp_server_arp = FALSE;
        adapter->m_dhcp_user_supplied_options_buffer_len = 0;

        // Adapter IP addr / netmask
        adapter->m_dhcp_addr = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[0];
        adapter->m_dhcp_netmask = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[1];

        // IP addr of DHCP masq server
        adapter->m_dhcp_server_ip = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[2];

        // Lease time in seconds
        adapter->m_dhcp_lease_time = ((IPADDR*)(Irp->AssociatedIrp.SystemBuffer))[3];

        GenerateRelatedMAC(adapter->m_dhcp_server_mac, adapter->CurrentAddress, 2);

        adapter->m_dhcp_enabled = TRUE;
        adapter->m_dhcp_server_arp = TRUE;

        CheckIfDhcpAndTunMode(adapter);

        Irp->IoStatus.Information = 1;  // Simple boolean value

        DEBUGP(("[TAP] Configured DHCP MASQ.\n"));
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
      }
    } break;

    case TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT: {
      if (inBufLength <= DHCP_USER_SUPPLIED_OPTIONS_BUFFER_SIZE && adapter->m_dhcp_enabled) {
        adapter->m_dhcp_user_supplied_options_buffer_len = 0;

        NdisMoveMemory(adapter->m_dhcp_user_supplied_options_buffer,
                       Irp->AssociatedIrp.SystemBuffer, inBufLength);

        adapter->m_dhcp_user_supplied_options_buffer_len = inBufLength;

        Irp->IoStatus.Information = 1;  // Simple boolean value

        DEBUGP(("[TAP] Set DHCP OPT.\n"));
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
      }
    } break;

    case TAP_WIN_IOCTL_GET_INFO: {
      char state[16];

      // Fetch adapter (miniport) state.
      if (tapAdapterSendAndReceiveReady(adapter) == NDIS_STATUS_SUCCESS)
        state[0] = 'A';
      else
        state[0] = 'a';

      if (tapAdapterReadAndWriteReady(adapter))
        state[1] = 'T';
      else
        state[1] = 't';

      state[2] = '0' + adapter->CurrentPowerState;

      if (adapter->MediaStateAlwaysConnected)
        state[3] = 'C';
      else
        state[3] = 'c';

      state[4] = '\0';

      // BUGBUG!!! What follows, and is not yet implemented, is a real mess.
      // BUGBUG!!! Tied closely to the NDIS 5 implementation. Need to map
      //    as much as possible to the NDIS 6 implementation.
      Irp->IoStatus.Status = ntStatus = RtlStringCchPrintfExA(
          ((LPTSTR)(Irp->AssociatedIrp.SystemBuffer)), outBufLength, NULL, NULL,
          STRSAFE_FILL_BEHIND_NULL | STRSAFE_IGNORE_NULLS,
#if PACKET_TRUNCATION_CHECK
          "State=%s Err=[%s/%d] #O=%d Tx=[%d,%d,%d] Rx=[%d,%d,%d] IrpQ=[%d,%d,%d] PktQ=[%d,%d,%d] "
          "InjQ=[%d,%d,%d]",
#else
          "State=%s Err=[%s/%d] #O=%d Tx=[%d,%d] Rx=[%d,%d] IrpQ=[%d,%d,%d] PktQ=[%d,%d,%d] "
          "InjQ=[%d,%d,%d]",
#endif
          state, g_LastErrorFilename, g_LastErrorLineNumber, (int)adapter->TapFileOpenCount,
          (int)(adapter->FramesTxDirected + adapter->FramesTxMulticast +
                adapter->FramesTxBroadcast),
          (int)adapter->TransmitFailuresOther,
#if PACKET_TRUNCATION_CHECK
          (int)adapter->m_TxTrunc,
#endif
          (int)adapter->m_Rx, (int)adapter->m_RxErr,
#if PACKET_TRUNCATION_CHECK
          (int)adapter->m_RxTrunc,
#endif
          (int)adapter->PendingReadIrpQueue.Count, (int)adapter->PendingReadIrpQueue.MaxCount,
          (int)IRP_QUEUE_SIZE,  // Ignored in NDIS 6 driver...

          (int)adapter->SendPacketQueue.Count, (int)adapter->SendPacketQueue.MaxCount,
          (int)PACKET_QUEUE_SIZE,

          (int)0,  // adapter->InjectPacketQueue.Count - Unused
          (int)0,  // adapter->InjectPacketQueue.MaxCount - Unused
          (int)INJECT_QUEUE_SIZE);

      Irp->IoStatus.Information = outBufLength;

      // BUGBUG!!! Fail because this is not completely implemented.
      ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    } break;

#if DBG
    case TAP_WIN_IOCTL_GET_LOG_LINE: {
      if (GetDebugLine((LPTSTR)Irp->AssociatedIrp.SystemBuffer, outBufLength)) {
        Irp->IoStatus.Status = ntStatus = STATUS_SUCCESS;
      } else {
        Irp->IoStatus.Status = ntStatus = STATUS_UNSUCCESSFUL;
      }

      Irp->IoStatus.Information = outBufLength;

      break;
    }
#endif

    case TAP_WIN_IOCTL_SET_MEDIA_STATUS: {
      if (inBufLength >= sizeof(ULONG)) {
        ULONG parm = ((PULONG)(Irp->AssociatedIrp.SystemBuffer))[0];
        tapSetMediaConnectStatus(adapter, (BOOLEAN)parm);
        Irp->IoStatus.Information = 1;
      } else {
        NOTE_ERROR();
        Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
      }
    } break;

    default:

      //
      // The specified I/O control code is unrecognized by this driver.
      //
      ntStatus = STATUS_INVALID_DEVICE_REQUEST;
      break;
  }

End:

  //
  // Finish the I/O operation by simply completing the packet and returning
  // the same status as in the packet itself.
  //
  Irp->IoStatus.Status = ntStatus;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  return ntStatus;
}

// Flush the pending read IRP queue.
VOID tapFlushIrpQueues(__in PTAP_ADAPTER_CONTEXT Adapter) {
  DEBUGP(("[TAP] tapFlushIrpQueues: Flushing %d pending read IRPs\n",
          Adapter->PendingReadIrpQueue.Count));

  tapIrpCsqFlush(&Adapter->PendingReadIrpQueue);
}

// IRP_MJ_CLEANUP
NTSTATUS
TapDeviceCleanup(PDEVICE_OBJECT DeviceObject, PIRP Irp)
/*++

Routine Description:

    Receipt of this request indicates that the last handle for a file
    object that is associated with the target device object has been closed
    (but, due to outstanding I/O requests, might not have been released).

    A driver that holds pending IRPs internally must implement a routine for
    IRP_MJ_CLEANUP. When the routine is called, the driver should cancel all
    the pending IRPs that belong to the file object identified by the IRP_MJ_CLEANUP
    call.

    In other words, it should cancel all the IRPs that have the same file-object
    pointer as the one supplied in the current I/O stack location of the IRP for the
    IRP_MJ_CLEANUP call. Of course, IRPs belonging to other file objects should
    not be canceled. Also, if an outstanding IRP is completed immediately, the
    driver does not have to cancel it.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    to be cleaned up.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/

{
  NDIS_STATUS status = NDIS_STATUS_SUCCESS;  // Always succeed.
  PIO_STACK_LOCATION irpSp;                  // Pointer to current stack location
  PTAP_ADAPTER_CONTEXT adapter = NULL;

  PAGED_CODE();

  DEBUGP(("[TAP] --> TapDeviceCleanup\n"));

  irpSp = IoGetCurrentIrpStackLocation(Irp);

  //
  // Fetch adapter context for this device.
  // --------------------------------------
  // Adapter pointer was stashed in FsContext when handle was opened.
  //
  adapter = (PTAP_ADAPTER_CONTEXT)(irpSp->FileObject)->FsContext;

  // Insure that adapter exists.
  ASSERT(adapter);

  if (adapter == NULL) {
    DEBUGP(("[TAP] release [%d.%d] cleanup request; adapter not found\n", TAP_DRIVER_MAJOR_VERSION,
            TAP_DRIVER_MINOR_VERSION));
  }

  if (adapter != NULL) {
    adapter->TapFileIsOpen = 0;  // Legacy...

    // Disconnect from media.
    tapSetMediaConnectStatus(adapter, FALSE);

    // Reset adapter state when cleaning up;
    tapResetAdapterState(adapter);

    // BUGBUG!!! Use RemoveLock???

    //
    // Flush pending send TAP packet queue.
    //
    tapFlushSendPacketQueue(adapter);

    ASSERT(adapter->SendPacketQueue.Count == 0);

    //
    // Flush the pending IRP queues
    //
    tapFlushIrpQueues(adapter);

    ASSERT(adapter->PendingReadIrpQueue.Count == 0);
  }

  // Complete the IRP.
  Irp->IoStatus.Status = status;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  DEBUGP(("[TAP] <-- TapDeviceCleanup; status = %8.8X\n", status));

  return status;
}

// IRP_MJ_CLOSE
NTSTATUS
TapDeviceClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
/*++

Routine Description:

    Receipt of this request indicates that the last handle of the file
    object that is associated with the target device object has been closed
    and released.

    All outstanding I/O requests have been completed or canceled.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    to be closed.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/

{
  NDIS_STATUS status = NDIS_STATUS_SUCCESS;  // Always succeed.
  PIO_STACK_LOCATION irpSp;                  // Pointer to current stack location
  PTAP_ADAPTER_CONTEXT adapter = NULL;

  PAGED_CODE();

  DEBUGP(("[TAP] --> TapDeviceClose\n"));

  irpSp = IoGetCurrentIrpStackLocation(Irp);

  //
  // Fetch adapter context for this device.
  // --------------------------------------
  // Adapter pointer was stashed in FsContext when handle was opened.
  //
  adapter = (PTAP_ADAPTER_CONTEXT)(irpSp->FileObject)->FsContext;

  // Insure that adapter exists.
  ASSERT(adapter);

  if (adapter == NULL) {
    DEBUGP(("[TAP] release [%d.%d] close request; adapter not found\n", TAP_DRIVER_MAJOR_VERSION,
            TAP_DRIVER_MINOR_VERSION));
  }

  if (adapter != NULL) {
    if (adapter->TapFileObject == NULL) {
      // Should never happen!!!
      ASSERT(FALSE);
    } else {
      ASSERT(irpSp->FileObject->FsContext == adapter);

      ASSERT(adapter->TapFileObject == irpSp->FileObject);
    }

    adapter->TapFileObject = NULL;
    irpSp->FileObject = NULL;

    // Remove reference added by when handle was opened.
    tapAdapterContextDereference(adapter);
  }

  // Complete the IRP.
  Irp->IoStatus.Status = status;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  DEBUGP(("[TAP] <-- TapDeviceClose; status = %8.8X\n", status));

  return status;
}

NTSTATUS
tapConcatenateNdisStrings(__inout PNDIS_STRING DestinationString,
                          __in_opt PNDIS_STRING SourceString1, __in_opt PNDIS_STRING SourceString2,
                          __in_opt PNDIS_STRING SourceString3) {
  NTSTATUS status;

  ASSERT(SourceString1 && SourceString2 && SourceString3);

  status = RtlAppendUnicodeStringToString(DestinationString, SourceString1);

  if (status == STATUS_SUCCESS) {
    status = RtlAppendUnicodeStringToString(DestinationString, SourceString2);

    if (status == STATUS_SUCCESS) {
      status = RtlAppendUnicodeStringToString(DestinationString, SourceString3);
    }
  }

  return status;
}

NTSTATUS
tapMakeDeviceNames(__in PTAP_ADAPTER_CONTEXT Adapter) {
  NDIS_STATUS status;
  NDIS_STRING deviceNamePrefix = NDIS_STRING_CONST("\\Device\\");
  NDIS_STRING tapNameSuffix = NDIS_STRING_CONST(".tap");

  // Generate DeviceName from NetCfgInstanceId.
  Adapter->DeviceName.Buffer = Adapter->DeviceNameBuffer;
  Adapter->DeviceName.MaximumLength = sizeof(Adapter->DeviceNameBuffer);

  status = tapConcatenateNdisStrings(&Adapter->DeviceName, &deviceNamePrefix,
                                     &Adapter->NetCfgInstanceId, &tapNameSuffix);

  if (status == STATUS_SUCCESS) {
    NDIS_STRING linkNamePrefix = NDIS_STRING_CONST("\\DosDevices\\Global\\");

    Adapter->LinkName.Buffer = Adapter->LinkNameBuffer;
    Adapter->LinkName.MaximumLength = sizeof(Adapter->LinkNameBuffer);

    status = tapConcatenateNdisStrings(&Adapter->LinkName, &linkNamePrefix,
                                       &Adapter->NetCfgInstanceId, &tapNameSuffix);
  }

  return status;
}

NDIS_STATUS
CreateTapDevice(__in PTAP_ADAPTER_CONTEXT Adapter) {
  NDIS_STATUS status;
  NDIS_DEVICE_OBJECT_ATTRIBUTES deviceAttribute;
  PDRIVER_DISPATCH dispatchTable[IRP_MJ_MAXIMUM_FUNCTION + 1];

  DEBUGP(("[TAP] version [%d.%d] creating tap device: %wZ\n", TAP_DRIVER_MAJOR_VERSION,
          TAP_DRIVER_MINOR_VERSION, &Adapter->NetCfgInstanceId));

  // Generate DeviceName and LinkName from NetCfgInstanceId.
  status = tapMakeDeviceNames(Adapter);

  if (NT_SUCCESS(status)) {
    DEBUGP(("[TAP] DeviceName: %wZ\n", &Adapter->DeviceName));
    DEBUGP(("[TAP] LinkName: %wZ\n", &Adapter->LinkName));

    // Initialize dispatch table.
    NdisZeroMemory(dispatchTable, (IRP_MJ_MAXIMUM_FUNCTION + 1) * sizeof(PDRIVER_DISPATCH));

    dispatchTable[IRP_MJ_CREATE] = TapDeviceCreate;
    dispatchTable[IRP_MJ_CLEANUP] = TapDeviceCleanup;
    dispatchTable[IRP_MJ_CLOSE] = TapDeviceClose;
    dispatchTable[IRP_MJ_READ] = TapDeviceRead;
    dispatchTable[IRP_MJ_WRITE] = TapDeviceWrite;
    dispatchTable[IRP_MJ_DEVICE_CONTROL] = TapDeviceControl;

    //
    // Create a device object and register dispatch handlers
    //
    NdisZeroMemory(&deviceAttribute, sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES));

    deviceAttribute.Header.Type = NDIS_OBJECT_TYPE_DEVICE_OBJECT_ATTRIBUTES;
    deviceAttribute.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1;
    deviceAttribute.Header.Size = sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES);

    deviceAttribute.DeviceName = &Adapter->DeviceName;
    deviceAttribute.SymbolicName = &Adapter->LinkName;
    deviceAttribute.MajorFunctions = &dispatchTable[0];
    // deviceAttribute.ExtensionSize = sizeof(FILTER_DEVICE_EXTENSION);

#if ENABLE_NONADMIN
    if (Adapter->AllowNonAdmin) {
      //
      // SDDL_DEVOBJ_SYS_ALL_WORLD_RWX_RES_RWX allows the kernel and system complete
      // control over the device. By default the admin can access the entire device,
      // but cannot change the ACL (the admin must take control of the device first)
      //
      // Everyone else, including "restricted" or "untrusted" code can read or write
      // to the device. Traversal beneath the device is also granted (removing it
      // would only effect storage devices, except if the "bypass-traversal"
      // privilege was revoked).
      //
      deviceAttribute.DefaultSDDLString = &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX;
    }
#endif

    status = NdisRegisterDeviceEx(Adapter->MiniportAdapterHandle, &deviceAttribute,
                                  &Adapter->DeviceObject, &Adapter->DeviceHandle);
  }

  ASSERT(NT_SUCCESS(status));

  if (NT_SUCCESS(status)) {
    // Set TAP device flags.
    (Adapter->DeviceObject)->Flags &= ~DO_BUFFERED_IO;
    (Adapter->DeviceObject)->Flags |= DO_DIRECT_IO;
    ;

    //========================
    // Finalize initialization
    //========================

    Adapter->TapDeviceCreated = TRUE;

    DEBUGP(("[%wZ] successfully created TAP device [%wZ]\n", &Adapter->NetCfgInstanceId,
            &Adapter->DeviceName));
  }

  DEBUGP(("[TAP] <-- CreateTapDevice; status = %8.8X\n", status));

  return status;
}

//
// DestroyTapDevice is called from AdapterHalt and NDIS miniport
// is in Halted state. Prior to entering the Halted state the
// miniport would have passed through the Pausing and Paused
// states. These miniport states have responsibility for waiting
// until NDIS network operations have completed.
//
VOID DestroyTapDevice(__in PTAP_ADAPTER_CONTEXT Adapter) {
  DEBUGP(("[TAP] --> DestroyTapDevice; Adapter: %wZ\n", &Adapter->NetCfgInstanceId));

  //
  // Let clients know we are shutting down
  //
  Adapter->TapDeviceCreated = FALSE;

  //
  // Flush pending send TAP packet queue.
  //
  tapFlushSendPacketQueue(Adapter);

  ASSERT(Adapter->SendPacketQueue.Count == 0);

  //
  // Flush IRP queues. Wait for pending I/O. Etc.
  // --------------------------------------------
  // Exhaust IRP and packet queues. Any pending IRPs will
  // be cancelled, causing user-space to get this error
  // on overlapped reads:
  //
  //   ERROR_OPERATION_ABORTED, code=995
  //
  //   "The I/O operation has been aborted because of either a
  //   thread exit or an application request."
  //
  // It's important that user-space close the device handle
  // when this code is returned, so that when we finally
  // do a NdisMDeregisterDeviceEx, the device reference count
  // is 0.  Otherwise the driver will not unload even if the
  // the last adapter has been halted.
  //
  // The act of flushing the queues at this point should result in the user-mode
  // application closing the adapter's device handle. Closing the handle will
  // result in the TapDeviceCleanup call being made, followed by the a call to
  // the TapDeviceClose callback.
  //
  tapFlushIrpQueues(Adapter);

  ASSERT(Adapter->PendingReadIrpQueue.Count == 0);

  //
  // Deregister the Win32 device.
  // ----------------------------
  // When a driver calls NdisDeregisterDeviceEx, the I/O manager deletes the
  // target device object if there are no outstanding references to it. However,
  // if any outstanding references remain, the I/O manager marks the device
  // object as "delete pending" and deletes the device object when the references
  // are finally released.
  //
  if (Adapter->DeviceHandle) {
    DEBUGP(("[TAP] Calling NdisDeregisterDeviceEx\n"));
    NdisDeregisterDeviceEx(Adapter->DeviceHandle);
  }

  Adapter->DeviceHandle = NULL;

  DEBUGP(("[TAP] <-- DestroyTapDevice\n"));
}
