// SoftEther VPN Source Code - Developer Edition Master Branch
// Kernel Device Driver


// NDIS5.c
// Description: Windows NDIS 5.0 Routine

#include <GlobalConst.h>

#define	NEO_DEVICE_DRIVER

#include "Neo.h"

static UINT max_speed = NEO_MAX_SPEED_DEFAULT;
static bool keep_link = false;

BOOLEAN
PsGetVersion(
			 PULONG MajorVersion OPTIONAL,
			 PULONG MinorVersion OPTIONAL,
			 PULONG BuildNumber OPTIONAL,
			 PUNICODE_STRING CSDVersion OPTIONAL
			 );

// Memory related
static NDIS_PHYSICAL_ADDRESS HighestAcceptableMax = NDIS_PHYSICAL_ADDRESS_CONST(-1, -1);
NDIS_HANDLE ndis_wrapper_handle = NULL;

// Whether Windows 8
bool g_is_win8 = false;

// Win32 driver entry point
NDIS_STATUS DriverEntry(DRIVER_OBJECT *DriverObject, UNICODE_STRING *RegistryPath)
{
	NDIS_MINIPORT_CHARACTERISTICS miniport;
	ULONG os_major_ver = 0, os_minor_ver = 0;

	// Initialize the Neo library
	if (NeoInit() == FALSE)
	{
		// Initialization Failed
		return STATUS_UNSUCCESSFUL;
	}

	g_is_win8 = false;

#ifndef	NDIS30_MINIPORT
	// Get the OS version
	PsGetVersion(&os_major_ver, &os_minor_ver, NULL, NULL);

	if (os_major_ver >= 7 || (os_major_ver == 6 && os_minor_ver >= 2))
	{
		// Windows 8
		g_is_win8 = true;
	}
#endif	// NDIS30_MINIPORT

	// Initialize the NDIS wrapper
	NdisMInitializeWrapper(&ctx->NdisWrapper, DriverObject, RegistryPath, NULL);
	ndis_wrapper_handle = ctx->NdisWrapper;

	// Register a NDIS miniport driver
	NeoZero(&miniport, sizeof(NDIS_MINIPORT_CHARACTERISTICS));
	miniport.MajorNdisVersion = NEO_NDIS_MAJOR_VERSION;
	miniport.MinorNdisVersion = NEO_NDIS_MINOR_VERSION;

	// Register the handler
	miniport.InitializeHandler = NeoNdisInit;
	miniport.HaltHandler = NeoNdisHalt;
	miniport.QueryInformationHandler = NeoNdisQuery;
	miniport.ResetHandler = NeoNdisReset;
	miniport.SetInformationHandler = NeoNdisSet;

#ifndef	NDIS30_MINIPORT
	miniport.SendPacketsHandler = NeoNdisSendPackets;
#else	// NDIS30_MINIPORT
	miniport.SendHandler = NULL;
#endif	// NDIS30_MINIPORT

	if (NG(NdisMRegisterMiniport(ctx->NdisWrapper, &miniport, sizeof(NDIS_MINIPORT_CHARACTERISTICS))))
	{
		// Registration failure
		return STATUS_UNSUCCESSFUL;
	}

	// Initialization success
	return STATUS_SUCCESS;
}

// Initialization handler of adapter
NDIS_STATUS NeoNdisInit(NDIS_STATUS *OpenErrorStatus,
					UINT *SelectedMediumIndex,
					NDIS_MEDIUM *MediumArray,
					UINT MediumArraySize,
					NDIS_HANDLE MiniportAdapterHandle,
					NDIS_HANDLE WrapperConfigurationContext)
{
	BOOL media_check;
	UINT i;

	if (ctx == NULL)
	{
		return NDIS_STATUS_FAILURE;
	}

	if (ctx->NdisWrapper == NULL)
	{
		ctx->NdisWrapper = ndis_wrapper_handle;
	}

	// Prevention of multiple start
	if (ctx->Initing != FALSE)
	{
		// Multiple started
		return NDIS_STATUS_FAILURE;
	}
	ctx->Initing = TRUE;

	// Examine whether it has already been initialized
	if (ctx->Inited != FALSE)
	{
		// Driver is started on another instance already.
		// PacketiX VPN driver can start only one instance per one service.
		// User can start multiple drivers with different instance ID
		return NDIS_STATUS_FAILURE;
	}

	// Current value of the packet filter
	ctx->CurrentPacketFilter = NDIS_PACKET_TYPE_ALL_LOCAL | NDIS_PACKET_TYPE_BROADCAST | NDIS_PACKET_TYPE_DIRECTED | NDIS_PACKET_TYPE_ALL_FUNCTIONAL;

	// Examine whether the Ethernet is available
	media_check = FALSE;
	for (i = 0;i < MediumArraySize;i++)
	{
		if (MediumArray[i] == NEO_MEDIA)
		{
			media_check = TRUE;
			break;
		}
	}
	if (media_check == FALSE)
	{
		// Ethernet is unavailable
		ctx->Initing = FALSE;
		return NDIS_STATUS_FAILURE;
	}

	// Media number to use
	*SelectedMediumIndex = i;

	// Initialize the adapter information
	ctx->NdisMiniport = MiniportAdapterHandle;
	ctx->NdisConfig = WrapperConfigurationContext;
	ctx->NdisContext = ctx;
	ctx->HardwareStatus = NdisHardwareStatusReady;
	ctx->Halting = FALSE;
	ctx->Connected = ctx->ConnectedOld = FALSE;

	if (keep_link == false)
	{
		ctx->ConnectedForce = TRUE;
	}

	// Read the information from the registry
	if (NeoLoadRegistry() == FALSE)
	{
		// Failure
		ctx->Initing = FALSE;
		return NDIS_STATUS_FAILURE;
	}

	// Register the device attributes

	if (g_is_win8 == false)
	{
		NdisMSetAttributes(ctx->NdisMiniport, ctx->NdisContext, FALSE, NdisInterfaceInternal);
	}
	else
	{
		NdisMSetAttributesEx(ctx->NdisMiniport, ctx->NdisContext, 16,
			NDIS_ATTRIBUTE_DESERIALIZE | NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT | NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT | NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND,
			NdisInterfaceInternal);
	}

	// Initialize the received packet array
	NeoInitPacketArray();

	// Initialize the control device
	NeoInitControlDevice();

	// Start the adapter
	NeoStartAdapter();

	// Flag setting
	ctx->Initing = FALSE;
	ctx->Inited = TRUE;

	// Notify the connection state
	NeoSetConnectState(FALSE);

	return NDIS_STATUS_SUCCESS;
}

// Open the device
BOOL NeoNdisOnOpen(IRP *irp, IO_STACK_LOCATION *stack)
{
	char name[MAX_SIZE];

	if (ctx == NULL)
	{
		return FALSE;
	}

	if (ctx->Opened != FALSE)
	{
		// Another client is connected already
		return FALSE;
	}
	ctx->Opened = TRUE;

	// Initialize the event name
	sprintf(name, NDIS_NEO_EVENT_NAME, ctx->HardwareID);

	// Register a Event
#ifndef	WIN9X
	ctx->Event = NeoNewEvent(name);
	if (ctx->Event == NULL)
	{
		ctx->Opened = FALSE;
		return FALSE;
	}
#endif	// WIN9X

	// Set the connection state
	NeoSetConnectState(TRUE);

	return TRUE;
}

// Close the device
BOOL NeoNdisOnClose(IRP *irp, IO_STACK_LOCATION *stack)
{
	if (ctx == NULL)
	{
		return FALSE;
	}

	if (ctx->Opened == FALSE)
	{
		// Client is not connected
		return FALSE;
	}
	ctx->Opened = FALSE;

	// Release the event
	NeoFreeEvent(ctx->Event);
	ctx->Event = NULL;

	// Release all packets
	NeoClearPacketQueue();

	NeoSetConnectState(FALSE);

	return TRUE;
}

// Crash
void NeoNdisCrash()
{
	NEO_QUEUE *q;
	q = (NEO_QUEUE *)0xACACACAC;
	q->Size = 128;
	NeoCopy(q->Buf, "ABCDEFG", 8);
}

// Dispatch table for control
NTSTATUS NeoNdisDispatch(DEVICE_OBJECT *DeviceObject, IRP *Irp)
{
	NTSTATUS status;
	IO_STACK_LOCATION *stack;
	void *buf;
	BOOL ok;
	status = STATUS_SUCCESS;

	if (ctx == NULL)
	{
		return NDIS_STATUS_FAILURE;
	}

	// Get the IRP stack
	stack = IoGetCurrentIrpStackLocation(Irp);

	// Initialize the number of bytes
	Irp->IoStatus.Information = 0;
	Irp->IoStatus.Status = STATUS_SUCCESS;

	buf = Irp->UserBuffer;

	if (ctx->Halting != FALSE)
	{
		// Device driver is terminating
		Irp->IoStatus.Information = STATUS_UNSUCCESSFUL;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return STATUS_UNSUCCESSFUL;
	}

	// Branch to each operation
	switch (stack->MajorFunction)
	{
	case IRP_MJ_CREATE:
		// Device is opened
		if (NeoNdisOnOpen(Irp, stack) == FALSE)
		{
			Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			status = STATUS_UNSUCCESSFUL;
		}
		break;

	case IRP_MJ_CLOSE:
		// Device is closed
		if (NeoNdisOnClose(Irp, stack) == FALSE)
		{
			Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			status = STATUS_UNSUCCESSFUL;
		}
		break;

	case IRP_MJ_READ:
#ifndef	WIN9X
		// Read (Reading of the received packet)
		ok = false;
		if (buf != NULL)
		{
			if (ctx->Opened && ctx->Inited)
			{
				if (stack->Parameters.Read.Length == NEO_EXCHANGE_BUFFER_SIZE)
				{
					// Address check
					bool check_ok = true;
					__try
					{
						ProbeForWrite(buf, NEO_EXCHANGE_BUFFER_SIZE, 1);
					}
					__except (EXCEPTION_EXECUTE_HANDLER)
					{
						check_ok = false;
					}

					if (check_ok)
					{
						MDL *mdl = IoAllocateMdl(buf, NEO_EXCHANGE_BUFFER_SIZE, false, false, NULL);

						if (mdl != NULL)
						{
							MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
						}


						// Read
						NeoRead(buf);
						Irp->IoStatus.Information = NEO_EXCHANGE_BUFFER_SIZE;
						ok = true;

						if (mdl != NULL)
						{
							MmUnlockPages(mdl);
							IoFreeMdl(mdl);
						}
					}
				}
			}
		}
		if (ok == FALSE)
		{
			// An error occurred
			Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			status = STATUS_UNSUCCESSFUL;
		}
#endif	// WIN9X
		break;

	case IRP_MJ_WRITE:
#ifndef	WIN9X
		// Write (Writing of a transmission packet)
		ok = false;
		if (buf != NULL)
		{
			if (ctx->Opened && ctx->Inited)
			{
				if (stack->Parameters.Write.Length == NEO_EXCHANGE_BUFFER_SIZE)
				{
					// Address check
					bool check_ok = true;
					__try
					{
						ProbeForRead(buf, NEO_EXCHANGE_BUFFER_SIZE, 1);
					}
					__except (EXCEPTION_EXECUTE_HANDLER)
					{
						check_ok = false;
					}

					if (check_ok)
					{
						MDL *mdl = IoAllocateMdl(buf, NEO_EXCHANGE_BUFFER_SIZE, false, false, NULL);

						if (mdl != NULL)
						{
							MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
						}

						ProbeForRead(buf, NEO_EXCHANGE_BUFFER_SIZE, 1);

						// Write
						NeoWrite(buf);
						Irp->IoStatus.Information = stack->Parameters.Write.Length;
						ok = true;

						if (mdl != NULL)
						{
							MmUnlockPages(mdl);
							IoFreeMdl(mdl);
						}
					}
				}
			}
		}
		if (ok == FALSE)
		{
			// An error occurred
			Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			status = STATUS_UNSUCCESSFUL;
		}
		break;
#endif	// WIN9X
	case IRP_MJ_DEVICE_CONTROL:
#ifdef	WIN9X
		// IO Control
		switch (stack->Parameters.DeviceIoControl.IoControlCode)
		{
		case NEO_IOCTL_SET_EVENT:
			// Specify a event
			if (Irp->AssociatedIrp.SystemBuffer == NULL ||
				stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(DWORD))
			{
				// An error occurred
				Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			}
			else
			{
				DWORD value = *((DWORD *)Irp->AssociatedIrp.SystemBuffer);
				ctx->Event = NeoCreateWin9xEvent(value);
				Irp->IoStatus.Information = sizeof(DWORD);
			}
			break;

		case NEO_IOCTL_PUT_PACKET:
			// Write a packet
			ok = false;
			buf = Irp->AssociatedIrp.SystemBuffer;
			if (buf != NULL)
			{
				if (stack->Parameters.DeviceIoControl.InputBufferLength == NEO_EXCHANGE_BUFFER_SIZE)
				{
					// Write
					NeoWrite(buf);
					Irp->IoStatus.Information = NEO_EXCHANGE_BUFFER_SIZE;
					ok = true;
				}
			}

			if (ok == false)
			{
				// An error occurred
				Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
				status = STATUS_UNSUCCESSFUL;
			}
			break;

		case NEO_IOCTL_GET_PACKET:
			// Get the packet
			ok = false;
			buf = Irp->AssociatedIrp.SystemBuffer;
			if (buf != NULL)
			{
				if (stack->Parameters.DeviceIoControl.OutputBufferLength == NEO_EXCHANGE_BUFFER_SIZE)
				{
					// Read
					NeoRead(buf);
					Irp->IoStatus.Information = NEO_EXCHANGE_BUFFER_SIZE;
					ok = true;
				}
			}

			if (ok == false)
			{
				// An error occurred
				Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
				status = STATUS_UNSUCCESSFUL;
			}
			break;
		}
#endif	// WIN9X
		break;
	}

	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return STATUS_SUCCESS;
}

// Initialize the control device
void NeoInitControlDevice()
{
	char name_kernel[MAX_SIZE];
	char name_win32[MAX_SIZE];
	UNICODE *unicode_kernel, *unicode_win32;
	DEVICE_OBJECT *control_device_object;
	NDIS_HANDLE ndis_control_handle;

	if (ctx == NULL)
	{
		return;
	}

	// Initialize the dispatch table
	NeoZero(ctx->DispatchTable, sizeof(PDRIVER_DISPATCH) * IRP_MJ_MAXIMUM_FUNCTION);

	// Register the handler
	ctx->DispatchTable[IRP_MJ_CREATE] =
		ctx->DispatchTable[IRP_MJ_CLOSE] =
		ctx->DispatchTable[IRP_MJ_READ] =
		ctx->DispatchTable[IRP_MJ_WRITE] =
		ctx->DispatchTable[IRP_MJ_DEVICE_CONTROL] = NeoNdisDispatch;
	ctx->Opened = FALSE;

	// Generate the device name
	sprintf(name_kernel, NDIS_NEO_DEVICE_NAME, ctx->HardwareID);
	unicode_kernel = NewUnicode(name_kernel);
	sprintf(name_win32, NDIS_NEO_DEVICE_NAME_WIN32, ctx->HardwareID);
	unicode_win32 = NewUnicode(name_win32);

	// Register the Device
	NdisMRegisterDevice(ctx->NdisWrapper, GetUnicode(unicode_kernel),
		GetUnicode(unicode_win32), ctx->DispatchTable,
		&control_device_object,
		&ndis_control_handle);

	ctx->NdisControlDevice = control_device_object;
	ctx->NdisControl = ndis_control_handle;

	// Initialize the display name
	if (strlen(ctx->HardwareID) > 11)
	{
		sprintf(ctx->HardwarePrintableID, NDIS_NEO_HARDWARE_ID, ctx->HardwareID_Raw + 11);
	}
	else
	{
		sprintf(ctx->HardwarePrintableID, NDIS_NEO_HARDWARE_ID, ctx->HardwareID_Raw);
	}
}

// Release the control device
void NeoFreeControlDevice()
{
	if (ctx == NULL)
	{
		return;
	}

	if (ctx->Opened != FALSE)
	{
		// Delete the event
		NeoSet(ctx->Event);
		NeoFreeEvent(ctx->Event);
		ctx->Event = NULL;
		ctx->Opened = FALSE;
	}
	// Delete the device
	NdisMDeregisterDevice(ctx->NdisControl);
}


// Read the information from the registry
BOOL NeoLoadRegistry()
{
	void *buf;
	NDIS_STATUS ret;
	UINT size;
	NDIS_HANDLE config;
	NDIS_CONFIGURATION_PARAMETER *param;
	UNICODE *name;
	ANSI_STRING ansi;
	UNICODE_STRING *unicode;
	UINT speed;
	BOOL keep;

	// Get the config handle
	NdisOpenConfiguration(&ret, &config, ctx->NdisConfig);
	if (NG(ret))
	{
		// Failure
		return FALSE;
	}

	// Read the MAC address
	NdisReadNetworkAddress(&ret, &buf, &size, config);
	if (NG(ret))
	{
		// Failure
		NdisCloseConfiguration(config);
		return FALSE;
	}

	// Copy the MAC address
	if (size != NEO_MAC_ADDRESS_SIZE)
	{
		// Invalid size
		NdisCloseConfiguration(config);
		return FALSE;
	}
	NeoCopy(ctx->MacAddress, buf, NEO_MAC_ADDRESS_SIZE);

	if (ctx->MacAddress[0] == 0x00 &&
		ctx->MacAddress[1] == 0x00 &&
		ctx->MacAddress[2] == 0x01 &&
		ctx->MacAddress[3] == 0x00 &&
		ctx->MacAddress[4] == 0x00 &&
		ctx->MacAddress[5] == 0x01)
	{
		// Special MAC address
		UINT ptr32 = (UINT)((UINT64)ctx);

		ctx->MacAddress[0] = 0x00;
		ctx->MacAddress[1] = 0xAD;
		ctx->MacAddress[2] = ((UCHAR *)(&ptr32))[0];
		ctx->MacAddress[3] = ((UCHAR *)(&ptr32))[1];
		ctx->MacAddress[4] = ((UCHAR *)(&ptr32))[2];
		ctx->MacAddress[5] = ((UCHAR *)(&ptr32))[3];
	}

	// Initialize the key name of the device name
	name = NewUnicode("MatchingDeviceId");

	// Read the hardware ID
	NdisReadConfiguration(&ret, &param, config, GetUnicode(name), NdisParameterString);
	FreeUnicode(name);
	if (NG(ret))
	{
		// Failure
		NdisCloseConfiguration(config);
		return FALSE;
	}
	// Type checking
	if (param->ParameterType != NdisParameterString)
	{
		// Failure
		NdisCloseConfiguration(config);
		return FALSE;
	}
	unicode = &param->ParameterData.StringData;

	// Prepare a buffer for ANSI string
	NeoZero(&ansi, sizeof(ANSI_STRING));
	ansi.MaximumLength = MAX_SIZE - 1;
	ansi.Buffer = NeoZeroMalloc(MAX_SIZE);

	// Convert to ANSI string
	NdisUnicodeStringToAnsiString(&ansi, unicode);
	// Copy
	strcpy(ctx->HardwareID, ansi.Buffer);
	strcpy(ctx->HardwareID_Raw, ctx->HardwareID);
	// Convert to upper case
	_strupr(ctx->HardwareID);
	// Release the memory
	NeoFree(ansi.Buffer);

	// Read the bit rate
	name = NewUnicode("MaxSpeed");
	NdisReadConfiguration(&ret, &param, config, GetUnicode(name), NdisParameterInteger);
	FreeUnicode(name);

	if (NG(ret) || param->ParameterType != NdisParameterInteger)
	{
		speed = NEO_MAX_SPEED_DEFAULT;
	}
	else
	{
		speed = param->ParameterData.IntegerData * 10000;
	}

	max_speed = speed;

	// Read the link keeping flag
	name = NewUnicode("KeepLink");
	NdisReadConfiguration(&ret, &param, config, GetUnicode(name), NdisParameterInteger);
	FreeUnicode(name);

	if (NG(ret) || param->ParameterType != NdisParameterInteger)
	{
		keep = false;
	}
	else
	{
		keep = (param->ParameterData.IntegerData == 0 ? false : true);
	}

	keep_link = keep;

	// Close the Config handle
	NdisCloseConfiguration(config);

	return TRUE;
}

// Stop handler of adapter
NDIS_STATUS NeoNdisHalt(NDIS_HANDLE MiniportAdapterContext)
{
	if (ctx == NULL)
	{
		return NDIS_STATUS_FAILURE;
	}

	if (ctx->Halting != FALSE)
	{
		// That has already been stopped
		return NDIS_STATUS_SUCCESS;
	}
	ctx->Halting = TRUE;

	// Stop the adapter
	NeoStopAdapter();

	// Release the packet array
	NeoFreePacketArray();

	// Delete the control device
	NeoFreeControlDevice();

	// Complete to stop
	ctx->Initing = ctx->Inited = FALSE;
	ctx->Connected = ctx->ConnectedForce = ctx->ConnectedOld = FALSE;
	ctx->Halting = FALSE;

	// Shutdown of Neo
	NeoShutdown();

	return NDIS_STATUS_SUCCESS;
}

// Reset handler of adapter
NDIS_STATUS NeoNdisReset(BOOLEAN *AddressingReset, NDIS_HANDLE MiniportAdapterContext)
{
	NdisMResetComplete(ctx->NdisMiniport, NDIS_STATUS_SUCCESS, FALSE);
	return NDIS_STATUS_SUCCESS;
}

// Information acquisition handler of adapter
NDIS_STATUS NeoNdisQuery(NDIS_HANDLE MiniportAdapterContext,
					NDIS_OID Oid,
					void *InformationBuffer,
					ULONG InformationBufferLength,
					ULONG *BytesWritten,
					ULONG *BytesNeeded)
{
	NDIS_MEDIUM media;
	void *buf;
	UINT value32;
	USHORT value16;
	UINT size;

	if (ctx == NULL)
	{
		return NDIS_STATUS_FAILURE;
	}

	// Initialization
	size = sizeof(UINT);
	value32 = value16 = 0;
	buf = &value32;

	// Branch processing
	switch (Oid)
	{
	case OID_GEN_SUPPORTED_LIST:
		// Return a list of supported OID
		buf = SupportedOids;
		size = sizeof(SupportedOids);
		break;

	case OID_GEN_MAC_OPTIONS:
		// Ethernet option
		value32 = NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | NDIS_MAC_OPTION_RECEIVE_SERIALIZED |
			NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | NDIS_MAC_OPTION_NO_LOOPBACK;
		break;

	case OID_GEN_HARDWARE_STATUS:
		// Hardware state
		buf = &ctx->HardwareStatus;
		size = sizeof(NDIS_HARDWARE_STATUS);
		break;

	case OID_GEN_MEDIA_SUPPORTED:
	case OID_GEN_MEDIA_IN_USE:
		// Type of media
		media = NdisMedium802_3;
		buf = &media;
		size = sizeof(NDIS_MEDIUM);
		break;

	case OID_GEN_CURRENT_LOOKAHEAD:
	case OID_GEN_MAXIMUM_LOOKAHEAD:
		// Available look-ahead size
		value32 = NEO_MAX_PACKET_SIZE_ANNOUNCE - NEO_MIN_PACKET_SIZE;
		break;

	case OID_GEN_MAXIMUM_FRAME_SIZE:
		// Maximum frame size
		value32 = NEO_MAX_PACKET_SIZE_ANNOUNCE - NEO_MIN_PACKET_SIZE;
		break;

	case OID_GEN_MAXIMUM_TOTAL_SIZE:
	case OID_GEN_TRANSMIT_BLOCK_SIZE:
	case OID_GEN_RECEIVE_BLOCK_SIZE:
		// Maximum packet size
		value32 = NEO_MAX_PACKET_SIZE_ANNOUNCE;
		break;

	case OID_GEN_TRANSMIT_BUFFER_SPACE:
	case OID_GEN_RECEIVE_BUFFER_SPACE:
		// Buffer size
		value32 = NEO_MAX_PACKET_SIZE_ANNOUNCE * NEO_MAX_PACKET_EXCHANGE;
		break;

	case OID_GEN_LINK_SPEED:
		// Communication speed
		value32 = max_speed;
		break;

	case OID_GEN_VENDOR_ID:
		// Vendor ID
		NeoCopy(&value32, ctx->MacAddress, 3);
		value32 &= 0xFFFFFF00;
		value32 |= 0x01;
		break;

	case OID_GEN_VENDOR_DESCRIPTION:
		// Hardware ID
		buf = ctx->HardwarePrintableID;
		size = (UINT)strlen(ctx->HardwarePrintableID) + 1;
		break;

	case OID_GEN_DRIVER_VERSION:
		// Driver version
		value16 = ((USHORT)NEO_NDIS_MAJOR_VERSION << 8) | NEO_NDIS_MINOR_VERSION;
		buf = &value16;
		size = sizeof(USHORT);
		break;

	case OID_GEN_VENDOR_DRIVER_VERSION:
		// Vendor driver version
		value16 = ((USHORT)NEO_NDIS_MAJOR_VERSION << 8) | NEO_NDIS_MINOR_VERSION;
		buf = &value16;
		size = sizeof(USHORT);
		break;

	case OID_802_3_PERMANENT_ADDRESS:
	case OID_802_3_CURRENT_ADDRESS:
		// MAC address
		buf = ctx->MacAddress;
		size = NEO_MAC_ADDRESS_SIZE;
		break;

	case OID_802_3_MAXIMUM_LIST_SIZE:
		// Number of multicast
		value32 = NEO_MAX_MULTICASE;
		break;

	case OID_GEN_MAXIMUM_SEND_PACKETS:
		// Number of packets that can be sent at a time
		value32 = NEO_MAX_PACKET_EXCHANGE;
		break;

	case OID_GEN_XMIT_OK:
		// Number of packets sent
		value32 = ctx->Status.NumPacketSend;
		break;

	case OID_GEN_RCV_OK:
		// Number of received packets
		value32 = ctx->Status.NumPacketRecv;
		break;

	case OID_GEN_XMIT_ERROR:
		// Number of transmission error packets
		value32 = ctx->Status.NumPacketSendError;
		break;

	case OID_GEN_RCV_ERROR:
		// Number of error packets received
		value32 = ctx->Status.NumPacketRecvError;
		break;

	case OID_GEN_RCV_NO_BUFFER:
		// Number of reception buffer shortage occurrences
		value32 = ctx->Status.NumPacketRecvNoBuffer;
		break;

	case OID_802_3_RCV_ERROR_ALIGNMENT:
		// Number of errors
		value32 = 0;
		break;

	case OID_GEN_MEDIA_CONNECT_STATUS:
		// Cable connection state
		NeoCheckConnectState();
		if (keep_link == false)
		{
			value32 = ctx->Connected ? NdisMediaStateConnected : NdisMediaStateDisconnected;
		}
		else
		{
			value32 = NdisMediaStateConnected;
		}
		break;

	case OID_802_3_XMIT_ONE_COLLISION:
	case OID_802_3_XMIT_MORE_COLLISIONS:
		// Number of collisions
		value32 = 0;
		break;

	case OID_GEN_CURRENT_PACKET_FILTER:
		// Current settings of the packet filter
		value32 = ctx->CurrentPacketFilter;
		break;

/*	case OID_GEN_PROTOCOL_OPTIONS:
		// Current value of the protocol option
		value32 = ctx->CurrentProtocolOptions;
		break;*/

	default:
		// Unknown OID
		*BytesWritten = 0;
		return NDIS_STATUS_INVALID_OID;
	}

	if (size > InformationBufferLength)
	{
		// Undersize
		*BytesNeeded = size;
		*BytesWritten = 0;
		return NDIS_STATUS_INVALID_LENGTH;
	}

	// Data copy
	NeoCopy(InformationBuffer, buf, size);
	*BytesWritten = size;

	return NDIS_STATUS_SUCCESS;
}

// Set the cable connection state
void NeoSetConnectState(BOOL connected)
{
	if (ctx == NULL)
	{
		return;
	}
	ctx->Connected = connected;
	NeoCheckConnectState();
}

// Check the cable connection state
void NeoCheckConnectState()
{
	if (ctx == NULL || ctx->NdisMiniport == NULL)
	{
		return;
	}

	if (keep_link == false)
	{
		if (ctx->ConnectedOld != ctx->Connected || ctx->ConnectedForce)
		{
			ctx->ConnectedForce = FALSE;
			ctx->ConnectedOld = ctx->Connected;
			if (ctx->Halting == FALSE)
			{
				NdisMIndicateStatus(ctx->NdisMiniport,
					ctx->Connected ? NDIS_STATUS_MEDIA_CONNECT : NDIS_STATUS_MEDIA_DISCONNECT,
					0, 0);
				NdisMIndicateStatusComplete(ctx->NdisMiniport);
			}
		}
	}
	else
	{
		if (ctx->ConnectedForce)
		{
			ctx->ConnectedForce = false;

			if (ctx->Halting == FALSE)
			{
				NdisMIndicateStatus(ctx->NdisMiniport,
					NDIS_STATUS_MEDIA_CONNECT,
					0, 0);
				NdisMIndicateStatusComplete(ctx->NdisMiniport);
			}
		}
	}
}

// Information setting handler of adapter
NDIS_STATUS NeoNdisSet(
					NDIS_HANDLE MiniportAdapterContext,
					NDIS_OID Oid,
					void *InformationBuffer,
					ULONG InformationBufferLength,
					ULONG *BytesRead,
					ULONG *BytesNeeded)
{
	if (ctx == NULL)
	{
		return STATUS_UNSUCCESSFUL;
	}

	// Initialization
	*BytesRead = 0;
	*BytesNeeded = 0;

	// Branch processing
	switch (Oid)
	{
	case OID_GEN_CURRENT_PACKET_FILTER:
		/* Packet filter */
		if (InformationBufferLength != 4)
		{
			*BytesNeeded = 4;
			return NDIS_STATUS_INVALID_LENGTH;
		}
		*BytesRead = 4;
		ctx->CurrentPacketFilter = *((UINT *)InformationBuffer);
		return NDIS_STATUS_SUCCESS;

//	case OID_GEN_PROTOCOL_OPTIONS:
		/* Current protocol option value */
/*		if (InformationBufferLength != 4)
		{
			*BytesNeeded = 4;
			return NDIS_STATUS_INVALID_LENGTH;
		}
		*BytesRead = 4;
		ctx->CurrentProtocolOptions = *((UINT *)InformationBuffer);
		return NDIS_STATUS_SUCCESS;*/

	case OID_GEN_CURRENT_LOOKAHEAD:
		/* Look ahead */
		if (InformationBufferLength != 4)
		{
			*BytesNeeded = 4;
			return NDIS_STATUS_INVALID_LENGTH;
		}
		*BytesRead = 4;
		return NDIS_STATUS_SUCCESS;

	case OID_802_3_MULTICAST_LIST:
		// Multicast list
		*BytesRead = InformationBufferLength;

		return NDIS_STATUS_SUCCESS;
	}

	return NDIS_STATUS_INVALID_OID;
}

// NDIS 3.0 packet send handler
NDIS_STATUS NeoNdisSend(NDIS_HANDLE MiniportAdapterContext,
						NDIS_PACKET *Packet, UINT Flags)
{
	NDIS_PACKET *PacketArray[1];
	PacketArray[0] = Packet;
	NeoNdisSendPackets(MiniportAdapterContext, PacketArray, 1);

	return NDIS_STATUS_SUCCESS;
}

// Packet send handler
void NeoNdisSendPackets(NDIS_HANDLE MiniportAdapterContext,
						NDIS_PACKET **PacketArray,
						UINT NumberOfPackets)
{
	UCHAR *Buf,*BufCopy;
	PNDIS_BUFFER Buffer;
	UCHAR *Tmp;
	UINT PacketLength;
	UINT CurrentLength;
	UINT i;

	if (ctx == NULL)
	{
		return;
	}

	// Update the connection state
	NeoCheckConnectState();

	if (NumberOfPackets == 0)
	{
		// The number of packets is 0
		return;
	}

	if (NeoNdisSendPacketsHaltCheck(PacketArray, NumberOfPackets) == FALSE)
	{
		// Device is stopped
		return;
	}

	// Operation of the packet queue
	NeoLockPacketQueue();
	{
		if (NeoNdisSendPacketsHaltCheck(PacketArray, NumberOfPackets) == FALSE)
		{
			// Device is stopped
			NeoUnlockPacketQueue();
			return;
		}

		// Place the packet in the queue in order
		for (i = 0;i < NumberOfPackets;i++)
		{
			// Get a packet
			NdisQueryPacket(PacketArray[i], NULL, NULL, &Buffer, &PacketLength);

			// Extract the packet.
			// Memory allocated here is used for the queue and is released at the time of releasing the queue.
			Buf = NeoMalloc(PacketLength);
			BufCopy = Buf;
			while (Buffer)
			{
				NdisQueryBuffer(Buffer, &Tmp, &CurrentLength);
				if (CurrentLength == 0)
				{
					// Complete
					break;
				}
				NeoCopy(BufCopy, Tmp, CurrentLength);
				BufCopy += CurrentLength;
				NdisGetNextBuffer(Buffer, &Buffer);
			}
			// Process this packet
			if (PacketLength > NEO_MIN_PACKET_SIZE)
			{
				if (PacketLength > NEO_MAX_PACKET_SIZE)
				{
					// Packet is too large
					NDIS_SET_PACKET_STATUS(PacketArray[i], NDIS_STATUS_FAILURE);

					if (g_is_win8)
					{
						NdisMSendComplete(ctx->NdisMiniport, PacketArray[i], NDIS_STATUS_FAILURE);
					}

					ctx->Status.NumPacketSendError++;
					NeoFree(Buf);
				}
				else
				{
					// Insert the packet into the queue
					NeoInsertQueue(Buf, PacketLength);
					NDIS_SET_PACKET_STATUS(PacketArray[i], NDIS_STATUS_SUCCESS);

					if (g_is_win8)
					{
						NdisMSendComplete(ctx->NdisMiniport, PacketArray[i], NDIS_STATUS_SUCCESS);
					}

					ctx->Status.NumPacketSend++;
				}
			}
			else
			{
				// Release if the packet doesn't contain data
				NDIS_SET_PACKET_STATUS(PacketArray[i], NDIS_STATUS_SUCCESS);

				if (g_is_win8)
				{
					NdisMSendComplete(ctx->NdisMiniport, PacketArray[i], NDIS_STATUS_SUCCESS);
				}

				NeoFree(Buf);
			}
		}
	}
	NeoUnlockPacketQueue();

	// Reception event
	NeoSet(ctx->Event);
}

// Stop check of packet transmission
BOOL NeoNdisSendPacketsHaltCheck(NDIS_PACKET **PacketArray, UINT NumberOfPackets)
{
	UINT i;

	if (ctx == NULL)
	{
		return FALSE;
	}

	if (ctx->Halting != FALSE || ctx->Opened == FALSE)
	{
		// Finishing
		for (i = 0;i < NumberOfPackets;i++)
		{
			NDIS_SET_PACKET_STATUS(PacketArray[i], NDIS_STATUS_FAILURE);

			if (g_is_win8)
			{
				NdisMSendComplete(ctx->NdisMiniport, PacketArray[i], NDIS_STATUS_SUCCESS);
			}

			ctx->Status.NumPacketSendError++;
		}
		return FALSE;
	}
	return TRUE;
}

// Initialize the packet array
void NeoInitPacketArray()
{
	UINT i;
	// Create a packet buffer
	for (i = 0;i < NEO_MAX_PACKET_EXCHANGE;i++)
	{
		ctx->PacketBuffer[i] = NeoNewPacketBuffer();
		// Store in the array
		ctx->PacketBufferArray[i] = ctx->PacketBuffer[i]->NdisPacket;
	}
}

// Release the packet array
void NeoFreePacketArray()
{
	UINT i;
	for (i = 0;i < NEO_MAX_PACKET_EXCHANGE;i++)
	{
		NeoFreePacketBuffer(ctx->PacketBuffer[i]);
		ctx->PacketBuffer[i] = NULL;
		ctx->PacketBufferArray[i] = NULL;
	}
}

// Release the packet buffer
void NeoFreePacketBuffer(PACKET_BUFFER *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

	// Detach the buffer from the packet
	NdisUnchainBufferAtFront(p->NdisPacket, &p->NdisBuffer);
	// Release the packet
	NdisFreePacket(p->NdisPacket);
	// Release the packet pool
	NdisFreePacketPool(p->PacketPool);
	// Release the buffer
	NdisFreeBuffer(p->NdisBuffer);
	// Release the memory
	NeoFree(p->Buf);
	// Release the buffer pool
	NdisFreeBufferPool(p->BufferPool);
	// Release the memory
	NeoFree(p);
}

// Create a packet buffer
PACKET_BUFFER *NeoNewPacketBuffer()
{
	PACKET_BUFFER *p;
	NDIS_STATUS ret;

	// Memory allocation
	p = NeoZeroMalloc(sizeof(PACKET_BUFFER));
	// Memory allocation for packet
	p->Buf = NeoMalloc(NEO_MAX_PACKET_SIZE);
	// Allocate the buffer pool
	NdisAllocateBufferPool(&ret, &p->BufferPool, 1);
	// Allocate the buffer
	NdisAllocateBuffer(&ret, &p->NdisBuffer, p->BufferPool, p->Buf, NEO_MAX_PACKET_SIZE);
	// Secure the packet pool
	NdisAllocatePacketPool(&ret, &p->PacketPool, 1, PROTOCOL_RESERVED_SIZE_IN_PACKET);
	// Secure the packet
	NdisAllocatePacket(&ret, &p->NdisPacket, p->PacketPool);
	NDIS_SET_PACKET_HEADER_SIZE(p->NdisPacket, NEO_PACKET_HEADER_SIZE);
	// Attach the buffer to the packet
	NdisChainBufferAtFront(p->NdisPacket, p->NdisBuffer);

	return p;
}

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

#ifndef	WIN9X
	KeResetEvent(event->event);
#else	// WIN9X
	if (event->win32_event != 0)
	{
		DWORD h = event->win32_event;
		_asm mov eax, h;
		VxDCall(_VWIN32_ResetWin32Event);
	}
#endif	// WIN9X
}

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

#ifndef	WIN9X
	KeSetEvent(event->event, 0, FALSE);
#else	// WIN9X
	if (event->win32_event != 0)
	{
		DWORD h = event->win32_event;
		_asm mov eax, h;
		VxDCall(_VWIN32_SetWin32Event);
	}
#endif	// WIN9X
}

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

#ifdef	WIN9X
	if (0)
	{
		if (event->win32_event != 0)
		{
			DWORD h = event->win32_event;
			_asm mov eax, h;
			VxDCall(_VWIN32_CloseVxDHandle);
		}
	}
#endif	WIN9X

	ZwClose(event->event_handle);

	// Release the memory
	NeoFree(event);
}

// Create a new event
#ifndef	WIN9X
NEO_EVENT *NeoNewEvent(char *name)
{
	UNICODE *unicode_name;
	NEO_EVENT *event;
	// Validate arguments
	if (name == NULL)
	{
		return NULL;
	}

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

	// Memory allocation
	event = NeoZeroMalloc(sizeof(NEO_EVENT));
	if (event == NULL)
	{
		FreeUnicode(unicode_name);
		return NULL;
	}

	// Create an Event
	event->event = IoCreateNotificationEvent(GetUnicode(unicode_name), &event->event_handle);
	if (event->event == NULL)
	{
		NeoFree(event);
		FreeUnicode(unicode_name);
		return NULL;
	}

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

	// Release a string
	FreeUnicode(unicode_name);

	return event;
}
#else	// WIN9X
NEO_EVENT *NeoCreateWin9xEvent(DWORD h)
{
	NEO_EVENT *event;
	// Validate arguments
	if (h == NULL)
	{
		return NULL;
	}

	// Memory allocation
	event = NeoZeroMalloc(sizeof(NEO_EVENT));
	if (event == NULL)
	{
		return NULL;
	}

	event->win32_event = h;

	return event;
}
#endif	// WIN9X

// Get the Unicode string
NDIS_STRING *GetUnicode(UNICODE *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return NULL;
	}

	return &u->String;
}

// Release the Unicode strings
void FreeUnicode(UNICODE *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

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

	// Release the memory
	NeoFree(u);
}

// Create a new Unicode string
UNICODE *NewUnicode(char *str)
{
	UNICODE *u;
	// Validate arguments
	if (str == NULL)
	{
		return NULL;
	}

	// Memory allocation
	u = NeoZeroMalloc(sizeof(UNICODE));
	if (u == NULL)
	{
		return NULL;
	}

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

	return u;
}

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

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

	// Release the memory
	NeoFree(lock);
}

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

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

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

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

// Create a new lock
NEO_LOCK *NeoNewLock()
{
	NDIS_SPIN_LOCK *spin_lock;

	// Memory allocation
	NEO_LOCK *lock = NeoZeroMalloc(sizeof(NEO_LOCK));
	if (lock == NULL)
	{
		return NULL;
	}

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

	NdisAllocateSpinLock(spin_lock);

	return lock;
}

// Memory copy
void NeoCopy(void *dst, void *src, UINT size)
{
	// Validate arguments
	if (dst == NULL || src == NULL || size == 0)
	{
		return;
	}

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

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

	// Clear
	NdisZeroMemory(dst, size);
}

// Clear to zero by memory allocation
void *NeoZeroMalloc(UINT size)
{
	void *p = NeoMalloc(size);
	if (p == NULL)
	{
		// Memory allocation failure
		return NULL;
	}
	// Clear to zero
	NeoZero(p, size);
	return p;
}

// Memory allocation
void *NeoMalloc(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;
}

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

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


