// SoftEther VPN Source Code - Developer Edition Master Branch
// SeLow: SoftEther Lightweight Network Protocol


// SeLow.c
// SeLow Device Driver

#include <GlobalConst.h>

#define	SELOW_DEVICE_DRIVER

#include "SeLow.h"

static SL_CTX sl_ctx = {0};
static SL_CTX *sl = &sl_ctx;

// Win32 driver entry point
NDIS_STATUS DriverEntry(DRIVER_OBJECT *driver_object, UNICODE_STRING *registry_path)
{
	NDIS_PROTOCOL_DRIVER_CHARACTERISTICS t;
	NDIS_STATUS ret = NDIS_STATUS_FAILURE;
	SL_UNICODE *protocol_name = NULL;
	NDIS_HANDLE protocol_handle = NULL;
	SL_CTX *sl_ctx = NULL;
	DEVICE_OBJECT *device_object = NULL;

	SlZero(sl, sizeof(SL_CTX));

	// Register the NDIS protocol
	protocol_name = SlNewUnicode(SL_PROTOCOL_NAME);
	if (protocol_name == NULL)
	{
		goto LABEL_CLEANUP;
	}

	SlZero(&t, sizeof(t));
	t.Header.Type = NDIS_OBJECT_TYPE_PROTOCOL_DRIVER_CHARACTERISTICS;
	t.Header.Revision = NDIS_PROTOCOL_DRIVER_CHARACTERISTICS_REVISION_2;
	t.Header.Size = NDIS_SIZEOF_PROTOCOL_DRIVER_CHARACTERISTICS_REVISION_2;
	t.MajorNdisVersion = 6;
	t.MinorNdisVersion = 20;
	t.Name = protocol_name->String;

	t.BindAdapterHandlerEx = SlNdisBindAdapterExProc;
	t.UnbindAdapterHandlerEx = SlNdisUnbindAdapterExProc;
	t.OpenAdapterCompleteHandlerEx = SlNdisOpenAdapterCompleteExProc;
	t.CloseAdapterCompleteHandlerEx = SlNdisCloseAdapterCompleteExProc;
	t.NetPnPEventHandler = SlNdisNetPnPEventProc;
	t.UninstallHandler = SlNdisUninstallProc;
	t.OidRequestCompleteHandler = SlNdisOidRequestCompleteProc;
	t.StatusHandlerEx = SlNdisStatusExProc;
	t.ReceiveNetBufferListsHandler = SlNdisReceiveNetBufferListsProc;
	t.SendNetBufferListsCompleteHandler = SlNdisSendNetBufferListsCompleteProc;

	// Create an adapters list
	sl->DriverObject = driver_object;
	sl->AdapterList = SlNewList();

	ret = NdisRegisterProtocolDriver(NULL, &t, &protocol_handle);

	if (NG(ret))
	{
		protocol_handle = NULL;
		goto LABEL_CLEANUP;
	}

	SlZero(driver_object->MajorFunction, sizeof(driver_object->MajorFunction));
	driver_object->MajorFunction[IRP_MJ_CREATE] = SlDeviceOpenProc;
	driver_object->MajorFunction[IRP_MJ_CLOSE] = SlDeviceCloseProc;
	driver_object->MajorFunction[IRP_MJ_READ] = SlDeviceReadProc;
	driver_object->MajorFunction[IRP_MJ_WRITE] = SlDeviceWriteProc;
	driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SlDeviceIoControlProc;
	driver_object->DriverUnload = SlUnloadProc;

	// Initialize the SL context
	sl->ProtocolHandle = protocol_handle;

	// Create a basic device
	sl->BasicDevice = SlNewDevice(SL_BASIC_DEVICE_NAME, SL_BASIC_DEVICE_NAME_SYMBOLIC);
	if (sl->BasicDevice == NULL)
	{
		ret = NDIS_STATUS_FAILURE;
		goto LABEL_CLEANUP;
	}
	sl->BasicDevice->IsBasicDevice = true;

LABEL_CLEANUP:

	SlFreeUnicode(protocol_name);

	if (NG(ret))
	{
		SlUnloadProc(driver_object);
	}

	return ret;
}

// Unloading procedure of the device driver
void SlUnloadProc(DRIVER_OBJECT *driver_object)
{
	// Release the protocol
	if (sl->ProtocolHandle != NULL)
	{
		NdisDeregisterProtocolDriver(sl->ProtocolHandle);
		sl->ProtocolHandle = NULL;
	}

	// Release the basic device
	SlFreeDevice(sl->BasicDevice);

	// Release the adapter list
	SlFreeList(sl->AdapterList);

	// Initialize the SL context
	SlZero(sl, sizeof(SL_CTX));
}

// Delete a device
void SlFreeDevice(SL_DEVICE *dev)
{
	NTSTATUS r;
	// Validate arguments
	if (dev == NULL)
	{
		return;
	}

	r = IoDeleteSymbolicLink(&dev->SymbolicLinkName->String);
	if (NG(r))
	{
		// May fail due to a bug in Windows Kernel
	}

	IoDeleteDevice(dev->DeviceObject);

	SlFreeUnicode(dev->DeviceName);
	SlFreeUnicode(dev->SymbolicLinkName);

	SlFreeLock(dev->OpenCloseLock);

	SlFree(dev);
}

// Create a new device
SL_DEVICE *SlNewDevice(char *device_name, char *symbolic_link_name)
{
	SL_UNICODE *u_device_name = SlNewUnicode(device_name);
	SL_UNICODE *u_sym_name = SlNewUnicode(symbolic_link_name);

	SL_DEVICE *ret = SlNewDeviceUnicode(u_device_name, u_sym_name);

	if (ret == NULL)
	{
		SlFreeUnicode(u_device_name);
		SlFreeUnicode(u_sym_name);
	}

	return ret;
}
SL_DEVICE *SlNewDeviceUnicode(SL_UNICODE *u_device_name, SL_UNICODE *u_sym_name)
{
	SL_DEVICE *ret = NULL;
	DEVICE_OBJECT *dev_obj = NULL;
	NTSTATUS r;
	SL_UNICODE *sddl;

	sddl = SlNewUnicode("D:P(A;;GA;;;SY)(A;;GA;;;BA)");

	/*r = IoCreateDevice(sl->DriverObject, sizeof(SL_DEVICE *),
		&u_device_name->String, FILE_DEVICE_TRANSPORT, 0, false, &dev_obj);*/

	r = IoCreateDeviceSecure(sl->DriverObject, sizeof(SL_DEVICE *),
		&u_device_name->String, FILE_DEVICE_TRANSPORT, 0, false, SlGetUnicode(sddl),
		NULL, &dev_obj);

	SlFreeUnicode(sddl);

	if (NG(r))
	{
		dev_obj = NULL;
		goto LABEL_CLEANUP;
	}

	r = IoCreateSymbolicLink(&u_sym_name->String, &u_device_name->String);
	if (NG(r))
	{
		// May fail due to a bug in Windows Kernel
	}

	ret = SlZeroMalloc(sizeof(SL_DEVICE));
	if (ret == NULL)
	{
		goto LABEL_CLEANUP;
	}

	ret->DeviceObject = dev_obj;
	ret->DeviceName = u_device_name;
	ret->SymbolicLinkName = u_sym_name;
	*((SL_DEVICE **)dev_obj->DeviceExtension) = ret;

	dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;

	ret->OpenCloseLock = SlNewLock();

LABEL_CLEANUP:
	if (ret == NULL)
	{
		if (dev_obj != NULL)
		{
			IoDeleteDevice(dev_obj);
		}
	}

	return ret;
}

// Device is opened
NTSTATUS SlDeviceOpenProc(DEVICE_OBJECT *device_object, IRP *irp)
{
	SL_DEVICE *dev = *((SL_DEVICE **)device_object->DeviceExtension);
	NTSTATUS ret = STATUS_UNSUCCESSFUL;
	IO_STACK_LOCATION *irp_stack = IoGetCurrentIrpStackLocation(irp);

	if (dev->IsBasicDevice)
	{
		// Basic device
		ret = STATUS_SUCCESS;
	}
	else
	{
		bool set_promisc = false;
		volatile UINT *num_pending_oid_requests = NULL;
		UINT64 v;
		char event_name[SL_EVENT_NAME_SIZE];
		char event_name_win32[SL_EVENT_NAME_SIZE];
		SL_EVENT *event_object = NULL;
		LARGE_INTEGER count;
		LARGE_INTEGER freq;

		count = KeQueryPerformanceCounter(&freq);

		InterlockedIncrement(&sl->IntCounter1);

		// Create a new event object
		v = (UINT64)device_object + (UINT64)(++sl->IntCounter1) + *((UINT64 *)(&count));
		sprintf(event_name, SL_EVENT_NAME, (UINT)v, (UINT)(v >> 32) + sl->IntCounter1);
		sprintf(event_name_win32, SL_EVENT_NAME_WIN32, (UINT)v, (UINT)(v >> 32) + sl->IntCounter1);
		event_object = SlNewEvent(event_name);

		SlLock(dev->OpenCloseLock);
		{
			// Add to the opened file list
			SlLockList(dev->FileList);
			{
				if (dev->Halting == false && dev->Adapter != NULL && dev->Adapter->Ready && dev->Adapter->Halt == false)
				{
					// Adapter device
					SL_FILE *f = SlZeroMalloc(sizeof(SL_FILE));
					NET_BUFFER_LIST_POOL_PARAMETERS p;

					f->Device = dev;
					f->Adapter = dev->Adapter;
					f->FileObject = irp_stack->FileObject;

					irp_stack->FileObject->FsContext = f;

					SlAdd(dev->FileList, f);

					ret = STATUS_SUCCESS;
					set_promisc = true;

					// Event
					f->Event = event_object;
					event_object = NULL;
					strcpy(f->EventNameWin32, event_name_win32);

					// Create a lock
					f->RecvLock = SlNewLock();

					// Create a NET_BUFFER_LIST pool
					SlZero(&p, sizeof(p));
					p.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
					p.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
					p.Header.Size = NDIS_SIZEOF_NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
					p.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT;
					p.fAllocateNetBuffer = true;
					p.ContextSize = 32 + sizeof(UINT32) * 12;
					p.DataSize = SL_MAX_PACKET_SIZE;
					p.PoolTag = 'SETH';

					f->NetBufferListPool = NdisAllocateNetBufferListPool(NULL, &p);

					num_pending_oid_requests = &dev->Adapter->NumPendingOidRequests;
				}
			}
			SlUnlockList(dev->FileList);
		}
		SlUnlock(dev->OpenCloseLock);

		if (event_object != NULL)
		{
			SlFreeEvent(event_object);
		}

		if (set_promisc)
		{
			// Enable promiscuous mode
			UINT filter = NDIS_PACKET_TYPE_PROMISCUOUS;
			SlSendOidRequest(dev->Adapter, true, OID_GEN_CURRENT_PACKET_FILTER, &filter, sizeof(filter));

			// Wait until the number of OID requests being processed becomes 0
			while ((*num_pending_oid_requests) != 0)
			{
				SlSleep(50);
			}
		}
	}

	irp->IoStatus.Status = ret;
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return ret;
}

// Send an OID request to the device
void SlSendOidRequest(SL_ADAPTER *a, bool set, NDIS_OID oid, void *data, UINT size)
{
	NDIS_OID_REQUEST *t;
	NDIS_STATUS ret;
	// Validate arguments
	if (a == NULL || data == NULL || size == 0)
	{
		return;
	}

	if (a->Halt == false)
	{
		bool ok = false;

		t = SlZeroMalloc(sizeof(NDIS_OID_REQUEST));

		t->Header.Type = NDIS_OBJECT_TYPE_OID_REQUEST;
		t->Header.Revision = NDIS_OID_REQUEST_REVISION_1;
		t->Header.Size = NDIS_SIZEOF_OID_REQUEST_REVISION_1;

		if (set == false)
		{
			t->RequestType = NdisRequestQueryInformation;
			t->DATA.QUERY_INFORMATION.Oid = oid;
			t->DATA.QUERY_INFORMATION.InformationBuffer = data;
			t->DATA.QUERY_INFORMATION.InformationBufferLength = size;
		}
		else
		{
			t->RequestType = NdisRequestSetInformation;
			t->DATA.SET_INFORMATION.Oid = oid;
			t->DATA.SET_INFORMATION.InformationBuffer = SlClone(data, size);
			t->DATA.SET_INFORMATION.InformationBufferLength = size;
		}

		SlLock(a->Lock);
		{
			if (a->AdapterHandle != NULL && a->Halt == false)
			{
				InterlockedIncrement(&a->NumPendingOidRequests);
				ok = true;
			}
		}
		SlUnlock(a->Lock);

		if (ok)
		{
			ret = NdisOidRequest(a->AdapterHandle, t);

			if (ret != NDIS_STATUS_PENDING)
			{
				InterlockedDecrement(&a->NumPendingOidRequests);
				if (set)
				{
					SlFree(t->DATA.SET_INFORMATION.InformationBuffer);
				}
				SlFree(t);
			}
		}
		else
		{
			if (set)
			{
				SlFree(t->DATA.SET_INFORMATION.InformationBuffer);
			}
			SlFree(t);
		}
	}
}

// Device is closed
NTSTATUS SlDeviceCloseProc(DEVICE_OBJECT *device_object, IRP *irp)
{
	SL_DEVICE *dev = *((SL_DEVICE **)device_object->DeviceExtension);
	NTSTATUS ret = STATUS_UNSUCCESSFUL;
	IO_STACK_LOCATION *irp_stack = IoGetCurrentIrpStackLocation(irp);

	if (dev->IsBasicDevice)
	{
		// Basic device
		ret = STATUS_SUCCESS;
	}
	else
	{
		// Adapter device
		SL_FILE *f = irp_stack->FileObject->FsContext;

		if (f != NULL)
		{
			bool clear_filter = false;

			// Wait until the number of packet being sent becomes the zero
			while (true)
			{
				if (f->NumSendingPackets == 0)
				{
					break;
				}

				SlSleep(50);
			}

			SlLock(dev->OpenCloseLock);
			{
				// Delete the file from the list
				SlLockList(dev->FileList);
				{
					SlDelete(dev->FileList, f);

					if (SL_LIST_NUM(dev->FileList) == 0)
					{
						// Clear the filter when all files are closed
						clear_filter = true;
					}
				}
				SlUnlockList(dev->FileList);

				if (dev->Adapter->Halt)
				{
					clear_filter = false;
				}

				if (clear_filter)
				{
					InterlockedIncrement(&dev->Adapter->NumPendingOidRequests);
				}
			}
			SlUnlock(dev->OpenCloseLock);

			if (clear_filter)
			{
				// Clear the filter when all files are closed
				UINT filter = 0;
				SlSendOidRequest(dev->Adapter, true, OID_GEN_CURRENT_PACKET_FILTER, &filter, sizeof(filter));
				InterlockedDecrement(&dev->Adapter->NumPendingOidRequests);
			}

			// Release the event
			SlFreeEvent(f->Event);

			// Release the receive queue
			if (true)
			{
				SL_PACKET *p = f->RecvPacketHead;

				while (p != NULL)
				{
					SL_PACKET *p_next = p->Next;

					SlFree(p);

					p = p_next;
				}
			}

			// Release the NET_BUFFER_LIST pool
			NdisFreeNetBufferListPool(f->NetBufferListPool);

			// Release the lock
			SlFreeLock(f->RecvLock);

			SlFree(f);

			ret = STATUS_SUCCESS;
		}
	}

	irp->IoStatus.Status = ret;
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return ret;
}

// Read procedure of the device
NTSTATUS SlDeviceReadProc(DEVICE_OBJECT *device_object, IRP *irp)
{
	SL_DEVICE *dev = *((SL_DEVICE **)device_object->DeviceExtension);
	NTSTATUS ret = STATUS_UNSUCCESSFUL;
	UINT ret_size = 0;
	IO_STACK_LOCATION *irp_stack = IoGetCurrentIrpStackLocation(irp);

	if (dev->IsBasicDevice)
	{
		// Return the adapter list in the case of basic device
		if (irp_stack->Parameters.Read.Length >= sizeof(SL_ADAPTER_INFO_LIST))
		{
			SL_ADAPTER_INFO_LIST *dst = irp->UserBuffer;

			if (dst != NULL)
			{
				// Address check
				bool check_ok = true;
				__try
				{
					ProbeForWrite(irp->UserBuffer, sizeof(SL_ADAPTER_INFO_LIST), 1);
				}
				__except (EXCEPTION_EXECUTE_HANDLER)
				{
					check_ok = false;
				}

				if (check_ok)
				{
					MDL *mdl;

					mdl = IoAllocateMdl(dst, irp_stack->Parameters.Read.Length, false, false, NULL);
					if (mdl != NULL)
					{
						MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
					}

					SlZero(dst, sizeof(SL_ADAPTER_INFO_LIST));

					dst->Signature = SL_SIGNATURE;
					dst->SeLowVersion = SL_VER;
					dst->EnumCompleted = sl->IsEnumCompleted ? 8 : 1;

					SlLockList(sl->AdapterList);
					{
						UINT i;

						dst->NumAdapters = MIN(SL_LIST_NUM(sl->AdapterList), SL_MAX_ADAPTER_INFO_LIST_ENTRY);

						for (i = 0;i < dst->NumAdapters;i++)
						{
							SL_ADAPTER *a = SL_LIST_DATA(sl->AdapterList, i);
							SL_ADAPTER_INFO *d = &dst->Adapters[i];

							d->MtuSize = a->MtuSize;
							SlCopy(d->MacAddress, a->MacAddress, 6);
							SlCopy(d->AdapterId, a->AdapterId, sizeof(a->AdapterId));
							strcpy(d->FriendlyName, a->FriendlyName);
							d->SupportsVLanHw = a->SupportVLan;
						}
					}
					SlUnlockList(sl->AdapterList);

					ret_size = sizeof(SL_ADAPTER_INFO);
					ret = STATUS_SUCCESS;

					if (mdl != NULL)
					{
						MmUnlockPages(mdl);
						IoFreeMdl(mdl);
					}
				}
			}
		}
	}
	else
	{
		// Adapter device
		SL_FILE *f = irp_stack->FileObject->FsContext;

		if (irp_stack->Parameters.Read.Length == SL_EXCHANGE_BUFFER_SIZE)
		{
			UCHAR *buf = irp->UserBuffer;

			if (dev->Halting || f->Adapter->Halt || buf == NULL)
			{
				// Halting
			}
			else
			{
				// Address check
				bool check_ok = true;
				__try
				{
					ProbeForWrite(irp->UserBuffer, SL_EXCHANGE_BUFFER_SIZE, 1);
				}
				__except (EXCEPTION_EXECUTE_HANDLER)
				{
					check_ok = false;
				}

				if (check_ok)
				{
					UINT num = 0;
					bool left = true;
					MDL *mdl;

					mdl = IoAllocateMdl(buf, SL_EXCHANGE_BUFFER_SIZE, false, false, NULL);
					if (mdl != NULL)
					{
						MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
					}

					// Lock the receive queue
					SlLock(f->RecvLock);
					{
						while (true)
						{
							SL_PACKET *q;
							if (num >= SL_MAX_PACKET_EXCHANGE)
							{
								if (f->RecvPacketHead == NULL)
								{
									left = false;
								}
								break;
							}
							q = f->RecvPacketHead;
							if (q != NULL)
							{
								f->RecvPacketHead = f->RecvPacketHead->Next;
								q->Next = NULL;
								f->NumRecvPackets--;

								if (f->RecvPacketHead == NULL)
								{
									f->RecvPacketTail = NULL;
								}
							}
							else
							{
								left = false;
								break;
							}
							SL_SIZE_OF_PACKET(buf, num) = q->Size;
							SlCopy(SL_ADDR_OF_PACKET(buf, num), q->Data, q->Size);
							num++;
							SlFree(q);
						}
					}
					SlUnlock(f->RecvLock);

					if (mdl != NULL)
					{
						MmUnlockPages(mdl);
						IoFreeMdl(mdl);
					}

					SL_NUM_PACKET(buf) = num;
					SL_LEFT_FLAG(buf) = left;

					if (left == false)
					{
						SlReset(f->Event);
					}
					else
					{
						SlSet(f->Event);
					}

					ret = STATUS_SUCCESS;
					ret_size = SL_EXCHANGE_BUFFER_SIZE;
				}
			}
		}
	}

	irp->IoStatus.Status = ret;
	irp->IoStatus.Information = ret_size;
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return ret;
}

// Write procedure of the device
NTSTATUS SlDeviceWriteProc(DEVICE_OBJECT *device_object, IRP *irp)
{
	SL_DEVICE *dev = *((SL_DEVICE **)device_object->DeviceExtension);
	NTSTATUS ret = STATUS_UNSUCCESSFUL;
	IO_STACK_LOCATION *irp_stack = IoGetCurrentIrpStackLocation(irp);
	UINT ret_size = 0;

	if (dev->IsBasicDevice == false)
	{
		// Adapter device
		SL_FILE *f = irp_stack->FileObject->FsContext;

		if (irp_stack->Parameters.Write.Length == SL_EXCHANGE_BUFFER_SIZE)
		{
			UCHAR *buf = irp->UserBuffer;

			if (dev->Halting || dev->Adapter->Halt || buf == NULL)
			{
				// Halting
			}
			else
			{
				// Address check
				bool check_ok = true;
				__try
				{
					ProbeForRead(irp->UserBuffer, SL_EXCHANGE_BUFFER_SIZE, 1);
				}
				__except (EXCEPTION_EXECUTE_HANDLER)
				{
					check_ok = false;
				}

				if (check_ok)
				{
					// Write the packet
					MDL *mdl;
					UINT num = SL_NUM_PACKET(buf);


					mdl = IoAllocateMdl(buf, SL_EXCHANGE_BUFFER_SIZE, false, false, NULL);
					if (mdl != NULL)
					{
						MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
					}

					ret = true;
					ret_size = SL_EXCHANGE_BUFFER_SIZE;

					if (num >= 1 && num <= SL_MAX_PACKET_EXCHANGE)
					{
						UINT i, j;
						NET_BUFFER_LIST *nbl_head = NULL;
						NET_BUFFER_LIST *nbl_tail = NULL;
						UINT num_packets = 0;
						NDIS_HANDLE adapter_handle = NULL;

						SlLock(f->Adapter->Lock);

						if (f->Adapter->NumPendingSendPackets <= SL_MAX_PACKET_QUEUED)
						{
							// Admit to send only if the number of packets being transmitted does not exceed the specified limit
							adapter_handle = f->Adapter->AdapterHandle;
						}

						if (adapter_handle != NULL)
						{
							// Lock the file list which opens the same adapter
							SlLockList(dev->FileList);
							for (j = 0;j < SL_LIST_NUM(dev->FileList);j++)
							{
								SL_FILE *other = SL_LIST_DATA(dev->FileList, j);

								if (other != f)
								{
									// Lock the receive queue of other file lists
									SlLock(other->RecvLock);

									other->SetEventFlag = false;
								}
							}

							for (i = 0;i < num;i++)
							{
								UINT packet_size = SL_SIZE_OF_PACKET(buf, i);
								UCHAR *packet_buf;
								NET_BUFFER_LIST *nbl = NULL;
								bool ok = false;
								bool is_vlan = false;
								UINT vlan_id = 0;
								UINT vlan_user_priority = 0, vlan_can_format_id = 0;

								if (packet_size > SL_MAX_PACKET_SIZE)
								{
									packet_size = SL_MAX_PACKET_SIZE;
								}
								else if (packet_size < SL_PACKET_HEADER_SIZE)
								{
									packet_size = SL_PACKET_HEADER_SIZE;
								}

								packet_buf = (UCHAR *)SL_ADDR_OF_PACKET(buf, i);

								for (j = 0;j < SL_LIST_NUM(dev->FileList);j++)
								{
									SL_FILE *other = SL_LIST_DATA(dev->FileList, j);

									if (other != f)
									{
										// Insert into the receive queue of the other file lists
										if (other->NumRecvPackets < SL_MAX_PACKET_QUEUED)
										{
											SL_PACKET *q = SlMalloc(sizeof(SL_PACKET));

											SlCopy(q->Data, packet_buf, packet_size);
											q->Size = packet_size;
											q->Next = NULL;

											if (other->RecvPacketHead == NULL)
											{
												other->RecvPacketHead = q;
											}
											else
											{
												other->RecvPacketTail->Next = q;
											}

											other->RecvPacketTail = q;

											other->NumRecvPackets++;

											other->SetEventFlag = true;
										}
									}
								}

								// Allocate a new NET_BUFFER_LIST
								if (f->NetBufferListPool != NULL)
								{
									nbl = NdisAllocateNetBufferList(f->NetBufferListPool, 16, 0);

									if (nbl != NULL)
									{
										nbl->SourceHandle = adapter_handle;
									}
								}

								if (nbl != NULL)
								{
									// Get the NET_BUFFER from the NET_BUFFER_LIST
									NET_BUFFER *nb = NET_BUFFER_LIST_FIRST_NB(nbl);

									NET_BUFFER_LIST_NEXT_NBL(nbl) = NULL;

									// Determine if the packet is IEEE802.1Q tagged packet
									if (dev->Adapter->SupportVLan && packet_size >= 18)
									{
										if (packet_buf[12] == 0x81 && packet_buf[13] == 0x00)
										{
											USHORT tag_us = 0;

											((UCHAR *)(&tag_us))[0] = packet_buf[15];
											((UCHAR *)(&tag_us))[1] = packet_buf[14];

											vlan_id = tag_us & 0x0FFF;
											vlan_user_priority = (tag_us >> 13) & 0x07;
											vlan_can_format_id = (tag_us >> 12) & 0x01;

											if (vlan_id != 0)
											{
												is_vlan = true;
											}
										}
									}

									if (is_vlan)
									{
										packet_size -= 4;
									}

									if (nb != NULL && OK(NdisRetreatNetBufferDataStart(nb, packet_size, 0, NULL)))
									{
										// Buffer copy
										UCHAR *dst = NdisGetDataBuffer(nb, packet_size, NULL, 1, 0);

										if (dst != NULL)
										{
											if (is_vlan == false)
											{
												SlCopy(dst, packet_buf, packet_size);
											}
											else
											{
												SlCopy(dst, packet_buf, 12);
												SlCopy(dst + 12, packet_buf + 16, packet_size + 4 - 16);
											}

											ok = true;
										}
										else
										{
											NdisAdvanceNetBufferDataStart(nb, packet_size, false, NULL);
										}
									}
								}

								if (ok == false)
								{
									if (nbl != NULL)
									{
										NdisFreeNetBufferList(nbl);
									}
								}
								else
								{
									if (nbl_head == NULL)
									{
										nbl_head = nbl;
									}

									if (nbl_tail != NULL)
									{
										NET_BUFFER_LIST_NEXT_NBL(nbl_tail) = nbl;
									}

									nbl_tail = nbl;

									((void **)NET_BUFFER_LIST_CONTEXT_DATA_START(nbl))[0] = f;

									if (is_vlan == false)
									{
										NET_BUFFER_LIST_INFO(nbl, Ieee8021QNetBufferListInfo) = NULL;
									}
									else
									{
										NDIS_NET_BUFFER_LIST_8021Q_INFO qinfo;

										qinfo.Value = &(((void **)NET_BUFFER_LIST_CONTEXT_DATA_START(nbl))[1]);
										SlZero(qinfo.Value, sizeof(UINT32) * 12);

										qinfo.TagHeader.VlanId = vlan_id;
										qinfo.TagHeader.UserPriority = vlan_user_priority;
										qinfo.TagHeader.CanonicalFormatId = vlan_can_format_id;

										NET_BUFFER_LIST_INFO(nbl, Ieee8021QNetBufferListInfo) = qinfo.Value;
									}

									num_packets++;
								}
							}

							for (j = 0;j < SL_LIST_NUM(dev->FileList);j++)
							{
								SL_FILE *other = SL_LIST_DATA(dev->FileList, j);

								if (other != f)
								{
									// Release the receive queue of other file lists
									SlUnlock(other->RecvLock);

									// Set an event
									if (other->SetEventFlag)
									{
										SlSet(other->Event);
									}
								}
							}
							SlUnlockList(dev->FileList);

							if (nbl_head != NULL)
							{
								InterlockedExchangeAdd(&f->NumSendingPackets, num_packets);
								InterlockedExchangeAdd(&f->Adapter->NumPendingSendPackets, num_packets);

								SlUnlock(f->Adapter->Lock);

								NdisSendNetBufferLists(adapter_handle, nbl_head, 0, 0);
							}
							else
							{
								SlUnlock(f->Adapter->Lock);
							}
						}
						else
						{
							SlUnlock(f->Adapter->Lock);
						}
					}

					if (mdl != NULL)
					{
						MmUnlockPages(mdl);
						IoFreeMdl(mdl);
					}
				}
			}
		}
	}

	irp->IoStatus.Information = ret_size;
	irp->IoStatus.Status = ret;
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return ret;
}

// IOCTL procedure of the device
NTSTATUS SlDeviceIoControlProc(DEVICE_OBJECT *device_object, IRP *irp)
{
	SL_DEVICE *dev = *((SL_DEVICE **)device_object->DeviceExtension);
	NTSTATUS ret = STATUS_UNSUCCESSFUL;
	IO_STACK_LOCATION *irp_stack = IoGetCurrentIrpStackLocation(irp);
	UINT ret_size = 0;

	if (dev->IsBasicDevice == false)
	{
		// Adapter device
		SL_FILE *f = irp_stack->FileObject->FsContext;

		switch (irp_stack->Parameters.DeviceIoControl.IoControlCode)
		{
		case SL_IOCTL_GET_EVENT_NAME:
			if (irp_stack->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(SL_IOCTL_EVENT_NAME))
			{
				SL_IOCTL_EVENT_NAME *t = irp->UserBuffer;

				if (t != NULL)
				{
					// Address check
					bool check_ok = true;
					__try
					{
						ProbeForWrite(t, sizeof(SL_IOCTL_EVENT_NAME), 1);
					}
					__except (EXCEPTION_EXECUTE_HANDLER)
					{
						check_ok = false;
					}

					if (check_ok)
					{
						strcpy(t->EventNameWin32, f->EventNameWin32);

						ret_size = sizeof(SL_IOCTL_EVENT_NAME);

						ret = STATUS_SUCCESS;
					}
				}
			}
			break;
		}
	}

	irp->IoStatus.Status = ret;
	irp->IoStatus.Information = ret_size;
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return ret;
}

// NDIS bind notification procedure
NDIS_STATUS SlNdisBindAdapterExProc(NDIS_HANDLE protocol_driver_context, NDIS_HANDLE bind_context, NDIS_BIND_PARAMETERS *bind_parameters)
{
	NDIS_STATUS ret = NDIS_STATUS_FAILURE;

	InterlockedIncrement(&sl->NumBoundAdapters);

	// Check the attributes of the adapter, and process only adapter which should be bound to
	if (bind_parameters->MediaType == NdisMedium802_3 &&
		bind_parameters->MacAddressLength == 6 &&
//		(bind_parameters->PhysicalMediumType == NdisPhysicalMedium802_3 || bind_parameters->PhysicalMediumType == 0) &&
		bind_parameters->AccessType == NET_IF_ACCESS_BROADCAST &&
		bind_parameters->DirectionType == NET_IF_DIRECTION_SENDRECEIVE &&
		bind_parameters->ConnectionType == NET_IF_CONNECTION_DEDICATED)
	{
		// Open the adapter
		NDIS_OPEN_PARAMETERS t;
		NDIS_MEDIUM medium_array = {NdisMedium802_3};
		SL_ADAPTER *a;
		wchar_t adapter_id_tag[] = SL_ADAPTER_ID_PREFIX_W;

		SlZero(&t, sizeof(t));
		t.Header.Type = NDIS_OBJECT_TYPE_OPEN_PARAMETERS;
		t.Header.Revision = NDIS_OPEN_PARAMETERS_REVISION_1;
		t.Header.Size = NDIS_SIZEOF_OPEN_PARAMETERS_REVSION_1;

		t.AdapterName = bind_parameters->AdapterName;
		t.MediumArray = &medium_array;
		t.MediumArraySize = 1;
		t.SelectedMediumIndex = &sl->DummyInt;
		t.FrameTypeArray = NULL;
		t.FrameTypeArraySize = 0;

		a = SlZeroMalloc(sizeof(SL_ADAPTER));

		a->Lock = SlNewLock();
		a->AdapterName = SlNewUnicodeFromUnicodeString(bind_parameters->AdapterName);

/*
		if (bind_parameters->MacOptions & NDIS_MAC_OPTION_8021Q_VLAN)
		{
			a->SupportVLan = true;
		}

		if (bind_parameters->TcpConnectionOffloadCapabilities != NULL)
		{
			if (bind_parameters->TcpConnectionOffloadCapabilities->Encapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q ||
				bind_parameters->TcpConnectionOffloadCapabilities->Encapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q_IN_OOB)
			{
				a->SupportVLan = true;
			}
		}
*/

		SlCopy(a->AdapterId, a->AdapterName->String.Buffer, MIN(sizeof(a->AdapterId) - sizeof(wchar_t), a->AdapterName->String.Length));
		SlCopy(a->AdapterId, adapter_id_tag, sizeof(adapter_id_tag) - sizeof(wchar_t));

		SlCopy(a->MacAddress, bind_parameters->CurrentMacAddress, 6);
		SlCopy(&a->BindParamCopy, bind_parameters, sizeof(NDIS_BIND_PARAMETERS));
		a->BindingContext = bind_context;
		a->MtuSize = bind_parameters->MtuSize;

		a->IsOpenPending = true;

		ret = NdisOpenAdapterEx(sl->ProtocolHandle, a, &t, bind_context, &a->AdapterHandle);
		a->AdapterHandle2 = a->AdapterHandle;

		if (ret != NDIS_STATUS_PENDING)
		{
			a->IsOpenPending = false;
			SlNdisOpenAdapterCompleteExProc(a, ret);
		}
	}

	if (ret != NDIS_STATUS_PENDING)
	{
		if (ret != NDIS_STATUS_SUCCESS)
		{
			InterlockedDecrement(&sl->NumBoundAdapters);
		}
	}

	return ret;
}

// Open success notification procedure of NDIS adapter
void SlNdisOpenAdapterCompleteExProc(NDIS_HANDLE protocol_binding_context, NDIS_STATUS status)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
	bool is_pending = a->IsOpenPending;
	NDIS_HANDLE binding_context = a->BindingContext;

	if (OK(status))
	{
		// Create an adapter device
		SL_UNICODE *device_name = SlNewUnicode(SL_ADAPTER_DEVICE_NAME);
		SL_UNICODE *symbolic_name = SlNewUnicode(SL_ADAPTER_DEVICE_NAME_SYMBOLIC);
		SL_DEVICE *dev;

		// Create a device name
		SlCopy(device_name->String.Buffer + 8, a->AdapterId, sizeof(wchar_t) * 46);
		SlCopy(symbolic_name->String.Buffer + 19, a->AdapterId, sizeof(wchar_t) * 46);

		dev = SlNewDeviceUnicode(device_name, symbolic_name);

		if (dev == NULL)
		{
			// Device creation failed
			SlFreeUnicode(device_name);
			SlFreeUnicode(symbolic_name);
		}
		else
		{
			// Create a file list
			dev->FileList = SlNewList();
		}
		if (dev != NULL)
		{
			// Get the display name
			SlSendOidRequest(a, false, OID_GEN_VENDOR_DESCRIPTION, a->FriendlyName,
				sizeof(a->FriendlyName) - 1);

			dev->Adapter = a;
			a->Device = dev;

			// Add this adapter to the adapter list
			SlLockList(sl->AdapterList);
			{
				SlAdd(sl->AdapterList, a);
			}
			SlUnlockList(sl->AdapterList);
		}
	}
	else
	{
		// Discard the adapter handle
		a->AdapterHandle = NULL;

		// Release the SL_ADAPTER
		SlFreeAdapter(a);

		a = NULL;
	}

	if (is_pending)
	{
		NdisCompleteBindAdapterEx(binding_context, status);
	}

	if (a != NULL)
	{
		a->Ready = true;
	}

	if (is_pending)
	{
		if (NG(status))
		{
			InterlockedDecrement(&sl->NumBoundAdapters);
		}
	}
}

// Release the SL_ADAPTER
void SlFreeAdapter(SL_ADAPTER *a)
{
	// Validate arguments
	if (a == NULL)
	{
		return;
	}

	SlFreeUnicode(a->AdapterName);

	SlFreeLock(a->Lock);

	SlFree(a);
}

// NDIS unbind notification procedure
NDIS_STATUS SlNdisUnbindAdapterExProc(NDIS_HANDLE unbind_context, NDIS_HANDLE protocol_binding_context)
{
	NDIS_STATUS ret;
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
	UINT j;
	NDIS_HANDLE adapter_handle = NULL;

	if (a->Halt)
	{
		//SL_WHERE;
	}

	adapter_handle = a->AdapterHandle;
	a->Halt = true;
	if (a->Device != NULL)
	{
		a->Device->Halting = true;
	}
	a->AdapterHandle = NULL;

	SlLock(a->Lock);
	{
	}
	SlUnlock(a->Lock);

	a->UnbindContext = unbind_context;
	a->IsClosePending = true;

	// Delete the adapter from the adapter list
	SlLockList(sl->AdapterList);
	{
		SlDelete(sl->AdapterList, a);
	}
	SlUnlockList(sl->AdapterList);

	for (j = 0;j < 32;j++)
	{
		// Wait until the number of OID requests of being processed by this adapter becomes zero
		while (true)
		{
			UINT num;

			num = a->NumPendingOidRequests;

			if (num == 0)
			{
				break;
			}
			else
			{
				j = 0;
			}

			//SlSleep(50);
		}

		// Wait until the number of packets this adapter is transmitting becomes zero
		while (true)
		{
			UINT num;

			num = a->NumPendingSendPackets;

			if (num == 0)
			{
				break;
			}
			else
			{
				j = 0;
			}

			//SlSleep(50);
		}

	}

	ret = NdisCloseAdapterEx(adapter_handle);

	if (ret != NDIS_STATUS_PENDING)
	{
		a->IsClosePending = false;
		SlNdisCloseAdapterCompleteExProc(a);

		ret = NDIS_STATUS_SUCCESS;

		InterlockedDecrement(&sl->NumBoundAdapters);
	}

	return ret;
}

// Close success notification procedure of NDIS adapter
void SlNdisCloseAdapterCompleteExProc(NDIS_HANDLE protocol_binding_context)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
	NDIS_HANDLE unbind_context = a->UnbindContext;
	bool is_pending = a->IsClosePending;
	UINT j;

	if (is_pending)
	{
		NdisCompleteUnbindAdapterEx(unbind_context);
	}

	for (j = 0;j < 32;j++)
	{
		if (a->Device != NULL)
		{
			a->Device->Halting = true;

			// Wait until the number of file handles that are associated with this device becomes zero
			while (true)
			{
				UINT num_files = 0;

				SlLock(a->Device->OpenCloseLock);
				{
					SlLockList(a->Device->FileList);
					{
						UINT i;
						num_files = SL_LIST_NUM(a->Device->FileList);

						for (i = 0;i < num_files;i++)
						{
							// Hit the associated event
							SL_FILE *f = SL_LIST_DATA(a->Device->FileList, i);

							if (f->FinalWakeUp == false)
							{
								SlSet(f->Event);
								f->FinalWakeUp = true;
							}
						}
					}
					SlUnlockList(a->Device->FileList);
				}
				SlUnlock(a->Device->OpenCloseLock);

				if (num_files == 0)
				{
					break;
				}

				SlSleep(50);
			}
		}
	}

	// Release the device
	if (a->Device != NULL)
	{
		// Delete the file list
		SlFreeList(a->Device->FileList);

		SlFreeDevice(a->Device);
		a->Device = NULL;
	}

	// Release the SL_ADAPTER
	SlFreeAdapter(a);

	if (is_pending)
	{
		InterlockedDecrement(&sl->NumBoundAdapters);
	}
}

// NDIS PnP notification procedure
NDIS_STATUS SlNdisNetPnPEventProc(NDIS_HANDLE protocol_binding_context, NET_PNP_EVENT_NOTIFICATION *net_pnp_event)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;

	if (net_pnp_event != NULL)
	{
		if (net_pnp_event->NetPnPEvent.NetEvent == NetEventBindsComplete)
		{
			sl->IsEnumCompleted = true;
		}
	}

	return NDIS_STATUS_SUCCESS;
}

// NDIS uninstall procedure
void SlNdisUninstallProc(void)
{
}

// NDIS OID request completion notification procedure
void SlNdisOidRequestCompleteProc(NDIS_HANDLE protocol_binding_context, NDIS_OID_REQUEST *oid_request, NDIS_STATUS status)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
	bool no_not_free = false;

	// Check the results
	if (oid_request->RequestType == NdisRequestQueryInformation)
	{
		if (oid_request->DATA.QUERY_INFORMATION.Oid == OID_GEN_VENDOR_DESCRIPTION)
		{
			no_not_free = true;
		}
	}

	// Release the memory
	if (no_not_free == false)
	{
		SlFree(oid_request->DATA.SET_INFORMATION.InformationBuffer);
	}

	SlFree(oid_request);

	// Counter subtraction
	InterlockedDecrement(&a->NumPendingOidRequests);
}

// NDIS status notification procedure
void SlNdisStatusExProc(NDIS_HANDLE protocol_binding_context, NDIS_STATUS_INDICATION *status_indication)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
}

// NDIS packet reception notification procedure
void SlNdisReceiveNetBufferListsProc(NDIS_HANDLE protocol_binding_context, NET_BUFFER_LIST *net_buffer_lists,
									 NDIS_PORT_NUMBER port_number, ULONG NumberOfNetBufferLists,
									 ULONG receive_flags)
{
	SL_ADAPTER *a = (SL_ADAPTER *)protocol_binding_context;
	UINT i;
	UINT return_flags = 0;
	NET_BUFFER_LIST *nbl;
	UCHAR *tmp_buffer;
	UINT tmp_size;

	if (net_buffer_lists == NULL || NumberOfNetBufferLists == 0)
	{
		return;
	}

	if (a->AdapterHandle2 == NULL)
	{
		a->AdapterHandle2 = a->AdapterHandle;
	}

	if (NDIS_TEST_RECEIVE_AT_DISPATCH_LEVEL(receive_flags))
	{
		NDIS_SET_RETURN_FLAG(return_flags, NDIS_RETURN_FLAGS_DISPATCH_LEVEL);
	}

	if (a->Halt || a->Device == NULL || a->Device->Halting || a->Ready == false || a->AdapterHandle == NULL)
	{
		goto LABEL_CLEANUP;
	}

	tmp_buffer = a->TmpBuffer;
	tmp_size = sizeof(a->TmpBuffer);

	nbl = net_buffer_lists;

	SlLockList(a->Device->FileList);
	{
		if (a->Halt == false)
		{
			for (i = 0;i < SL_LIST_NUM(a->Device->FileList);i++)
			{
				// Lock the receive queue
				SL_FILE *f = SL_LIST_DATA(a->Device->FileList, i);

				SlLock(f->RecvLock);
			}

			while (nbl != NULL)
			{
				NET_BUFFER *nb = NET_BUFFER_LIST_FIRST_NB(nbl);
				bool is_vlan = false;
				UCHAR vlan_tag[2];

				if (NET_BUFFER_LIST_INFO(nbl, Ieee8021QNetBufferListInfo) != 0)
				{
					NDIS_NET_BUFFER_LIST_8021Q_INFO qinfo;
					qinfo.Value = NET_BUFFER_LIST_INFO(nbl, Ieee8021QNetBufferListInfo);
					if (qinfo.TagHeader.VlanId != 0)
					{
						USHORT tag_us;
						is_vlan = true;

						a->SupportVLan = true;

						tag_us = (qinfo.TagHeader.UserPriority & 0x07 << 13) |
							(qinfo.TagHeader.CanonicalFormatId & 0x01 << 12) |
							(qinfo.TagHeader.VlanId & 0x0FFF);

						vlan_tag[0] = ((UCHAR *)(&tag_us))[1];
						vlan_tag[1] = ((UCHAR *)(&tag_us))[0];
					}
				}

				while (nb != NULL)
				{
					UINT size = NET_BUFFER_DATA_LENGTH(nb);

					if (size >= 14 && size <= tmp_size && size <= (UINT)((is_vlan == false) ? SL_MAX_PACKET_SIZE : (SL_MAX_PACKET_SIZE - 4)))
					{
						UCHAR *ptr = NdisGetDataBuffer(nb, size, tmp_buffer, 1, 0);

						if (ptr != NULL)
						{
							// Insert the queue to all waiting files
							for (i = 0;i < SL_LIST_NUM(a->Device->FileList);i++)
							{
								SL_FILE *f = SL_LIST_DATA(a->Device->FileList, i);

								if (f->NumRecvPackets < SL_MAX_PACKET_QUEUED)
								{
									SL_PACKET *q = SlMalloc(sizeof(SL_PACKET));

									if (is_vlan == false)
									{
										// Normal packet
										SlCopy(q->Data, ptr, size);
										q->Size = size;
									}
									else
									{
										// Insert a tag in the case of IEEE802.1Q packet
										SlCopy(q->Data, ptr, 12);
										q->Data[12] = 0x81;
										q->Data[13] = 0x00;
										SlCopy(&q->Data[14], vlan_tag, 2);
										SlCopy(&q->Data[16], &ptr[12], size - 12);

										q->Size = size + 4;
									}

									q->Next = NULL;

									if (f->RecvPacketHead == NULL)
									{
										f->RecvPacketHead = q;
									}
									else
									{
										f->RecvPacketTail->Next = q;
									}

									f->RecvPacketTail = q;

									f->NumRecvPackets++;
								}
							}
						}
					}

					nb = NET_BUFFER_NEXT_NB(nb);
				}

				nbl = NET_BUFFER_LIST_NEXT_NBL(nbl);
			}

			// Hit the event
			for (i = 0;i < SL_LIST_NUM(a->Device->FileList);i++)
			{
				SL_FILE *f = SL_LIST_DATA(a->Device->FileList, i);

				// Unlock the receive queue
				SlUnlock(f->RecvLock);

				SlSet(f->Event);
			}
		}
	}
	SlUnlockList(a->Device->FileList);

LABEL_CLEANUP:

	if (NDIS_TEST_RECEIVE_CAN_PEND(receive_flags))
	{
		NdisReturnNetBufferLists(a->AdapterHandle2, net_buffer_lists, return_flags);
	}
}

// NDIS packet transmission completion notification procedure
void SlNdisSendNetBufferListsCompleteProc(NDIS_HANDLE protocol_binding_context, NET_BUFFER_LIST *net_buffer_lists,
										  ULONG send_complete_flags)
{
	NET_BUFFER_LIST *nbl;

	nbl = net_buffer_lists;

	while (nbl != NULL)
	{
		NET_BUFFER_LIST *current_nbl = nbl;
		SL_FILE *f;
		NET_BUFFER *nb = NET_BUFFER_LIST_FIRST_NB(nbl);

		if (nb != NULL)
		{
			UINT size = NET_BUFFER_DATA_LENGTH(nb);

			NdisAdvanceNetBufferDataStart(nb, size, false, NULL);
		}

		// Get a file context
		f = ((void **)NET_BUFFER_LIST_CONTEXT_DATA_START(nbl))[0];

		nbl = NET_BUFFER_LIST_NEXT_NBL(nbl);
		NET_BUFFER_LIST_NEXT_NBL(current_nbl) = NULL;

		// Release the NET_BUFFER_LIST
		NdisFreeNetBufferList(current_nbl);

		// Reduce the number of packets being sent by 1
		InterlockedExchangeAdd(&f->NumSendingPackets, (LONG)-1);
		InterlockedExchangeAdd(&f->Adapter->NumPendingSendPackets, (LONG)-1);
	}
}

// Crash
void SlCrash(UINT a, UINT b, UINT c, UINT d)
{
	KeBugCheckEx(0x00000061, (ULONG_PTR)a, (ULONG_PTR)b, (ULONG_PTR)c, (ULONG_PTR)d);
}

// Memory allocation
void *SlMalloc(UINT size)
{
	NDIS_STATUS r;
	void *p;
	if (size == 0)
	{
		size = 1;
	}

	// Allocate the non-paged memory
	r = NdisAllocateMemoryWithTag(&p, size, 'SETH');

	if (NG(r))
	{
		return NULL;
	}

	return p;
}

// Clear to zero by allocating the memory
void *SlZeroMalloc(UINT size)
{
	void *p = SlMalloc(size);
	if (p == NULL)
	{
		// Memory allocation failure
		return NULL;
	}

	// Clear to zero
	SlZero(p, size);

	return p;
}

// Release the memory
void SlFree(void *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

	// Release the memory
	NdisFreeMemory(p, 0, 0);
}

// Memory zero clear
void SlZero(void *dst, UINT size)
{
	// Validate arguments
	if (dst == NULL || size == 0)
	{
		return;
	}

	// Clear
	NdisZeroMemory(dst, size);
}

// Copy memory
void SlCopy(void *dst, void *src, UINT size)
{
	// Validate arguments
	if (dst == NULL || src == NULL || size == 0)
	{
		return;
	}

	// Copy
	NdisMoveMemory(dst, src, size);
}

// Create a lock
SL_LOCK *SlNewLock()
{
	NDIS_SPIN_LOCK *spin_lock;

	// Memory allocation
	SL_LOCK *lock = SlZeroMalloc(sizeof(SL_LOCK));
	if (lock == NULL)
	{
		return NULL;
	}

	// Initialize spin lock
	spin_lock = &lock->spin_lock;

	NdisAllocateSpinLock(spin_lock);

	return lock;
}

// Lock
void SlLock(SL_LOCK *lock)
{
	NDIS_SPIN_LOCK *spin_lock;
	// Validate arguments
	if (lock == NULL)
	{
		return;
	}

	spin_lock = &lock->spin_lock;
	NdisAcquireSpinLock(spin_lock);
}

// Unlock
void SlUnlock(SL_LOCK *lock)
{
	NDIS_SPIN_LOCK *spin_lock;
	// Validate arguments
	if (lock == NULL)
	{
		return;
	}

	spin_lock = &lock->spin_lock;
	NdisReleaseSpinLock(spin_lock);
}

// Release the lock
void SlFreeLock(SL_LOCK *lock)
{
	NDIS_SPIN_LOCK *spin_lock;
	// Validate arguments
	if (lock == NULL)
	{
		return;
	}

	spin_lock = &lock->spin_lock;
	NdisFreeSpinLock(spin_lock);

	// Release the memory
	SlFree(lock);
}

// Create an event
SL_EVENT *SlNewEvent(char *name)
{
	SL_UNICODE *unicode_name;
	SL_EVENT *event;
	// Validate arguments
	if (name == NULL)
	{
		return NULL;
	}

	// Convert to Unicode name
	unicode_name = SlNewUnicode(name);
	if (unicode_name == NULL)
	{
		return NULL;
	}

	// Memory allocation
	event = SlZeroMalloc(sizeof(SL_EVENT));
	if (event == NULL)
	{
		SlFreeUnicode(unicode_name);
		return NULL;
	}

	// Create an event
	event->event = IoCreateNotificationEvent(SlGetUnicode(unicode_name), &event->event_handle);
	if (event->event == NULL)
	{
		SlFree(event);
		SlFreeUnicode(unicode_name);
		return NULL;
	}

	// Initialize the event
	KeInitializeEvent(event->event, NotificationEvent, FALSE);
	KeClearEvent(event->event);

	// Release the string
	SlFreeUnicode(unicode_name);

	return event;
}

// Release the event
void SlFreeEvent(SL_EVENT *event)
{
	// Validate arguments
	if (event == NULL)
	{
		return;
	}

	ZwClose(event->event_handle);

	// Release the memory
	SlFree(event);
}

// Set the event
void SlSet(SL_EVENT *event)
{
	// Validate arguments
	if (event == NULL)
	{
		return;
	}

	KeSetEvent(event->event, 0, FALSE);
}

// Reset the event
void SlReset(SL_EVENT *event)
{
	// Validate arguments
	if (event == NULL)
	{
		return;
	}

	KeResetEvent(event->event);
}

// Create by copying the Unicode
SL_UNICODE *SlNewUnicodeFromUnicodeString(UNICODE_STRING *src)
{
	SL_UNICODE *u;
	// Validate arguments
	if (src == NULL)
	{
		return NULL;
	}

	// Memory allocation
	u = SlZeroMalloc(sizeof(SL_UNICODE));
	if (u == NULL)
	{
		return NULL;
	}

	u->String.Length = u->String.MaximumLength = src->Length;
	
	u->String.Buffer = SlZeroMalloc(src->Length);
	SlCopy(u->String.Buffer, src->Buffer, src->Length);

	return u;
}

// Create a Unicode
SL_UNICODE *SlNewUnicode(char *str)
{
	SL_UNICODE *u;
	// Validate arguments
	if (str == NULL)
	{
		return NULL;
	}

	// Memory allocation
	u = SlZeroMalloc(sizeof(SL_UNICODE));
	if (u == NULL)
	{
		return NULL;
	}

	// String initialization
	NdisInitializeString(&u->String, str);

	return u;
}

// Release the Unicode
void SlFreeUnicode(SL_UNICODE *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	// Release the string
	NdisFreeString(u->String);

	// Release the memory
	SlFree(u);
}

// Get an Unicode
NDIS_STRING *SlGetUnicode(SL_UNICODE *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return NULL;
	}

	return &u->String;
}

// Create a list
SL_LIST *SlNewList()
{
	SL_LIST *o;

	o = (SL_LIST *)SlZeroMalloc(sizeof(SL_LIST));

	o->lock = SlNewLock();

	o->num_item = 0;
	o->num_reserved = SL_INIT_NUM_RESERVED;

	o->p = (void **)SlZeroMalloc(sizeof(void *) * o->num_reserved);

	return o;
}

// Add an element to the list
void SlAdd(SL_LIST *o, void *p)
{
	UINT i;
	// Validate arguments
	if (o == NULL || p == NULL)
	{
		return;
	}

	i = o->num_item;
	o->num_item++;

	if (o->num_item > o->num_reserved)
	{
		UINT old_num_reserved = o->num_reserved;
		void *p_old = o->p;

		o->num_reserved = o->num_reserved * 2;

		o->p = SlZeroMalloc(sizeof(void *) * o->num_reserved);
		SlCopy(o->p, p_old, sizeof(void *) * old_num_reserved);
		SlFree(p_old);
	}

	o->p[i] = p;
}

// Delete the element from the list
bool SlDelete(SL_LIST *o, void *p)
{
	UINT i, n;
	// Validate arguments
	if (o == NULL || p == NULL)
	{
		return false;
	}

	for (i = 0;i < o->num_item;i++)
	{
		if (o->p[i] == p)
		{
			break;
		}
	}
	if (i == o->num_item)
	{
		return false;
	}

	n = i;
	for (i = n;i < (o->num_item - 1);i++)
	{
		o->p[i] = o->p[i + 1];
	}
	o->num_item--;

	return true;
}

// Delete all elements from the list
void SlDeleteAll(SL_LIST *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	o->num_item = 0;
}

// Lock the list
void SlLockList(SL_LIST *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	SlLock(o->lock);
}

// Unlock the list
void SlUnlockList(SL_LIST *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	SlUnlock(o->lock);
}

// Release the list
void SlFreeList(SL_LIST *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	SlFree(o->p);
	SlFreeLock(o->lock);

	SlFree(o);
}

// Clone the memory
void *SlClone(void *p, UINT size)
{
	void *ret;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

	ret = SlMalloc(size);
	SlCopy(ret, p, size);

	return ret;
}


// Sleep
void SlSleep(int milliSeconds)
{
	PKTIMER timer = SlMalloc(sizeof(KTIMER));
	LARGE_INTEGER duetime;

	duetime.QuadPart = (__int64)milliSeconds * -10000;
	KeInitializeTimerEx(timer, NotificationTimer);
	KeSetTimerEx(timer, duetime, 0, NULL);

	KeWaitForSingleObject(timer, Executive, KernelMode, FALSE, NULL);

	SlFree(timer);
}



