// Hid.c: HID-related routine

#include <driver.h>
#include <StaticHidRegistry.h>
#include "Hid.tmh"

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpGetHidDescriptor(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{

	NTSTATUS        status   = STATUS_SUCCESS;
	PDEVICE_CONTEXT pContext = DeviceGetContext(Device);
	size_t			szHidDescriptor = 0;
	WDFMEMORY       RequestMemory;
	PHID_DESCRIPTOR pSelectedHidDescriptor = NULL;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = WdfRequestRetrieveOutputMemory(
		Request, 
		&RequestMemory
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", 
			status
		);
		return status;
	}

	switch (pContext->DeviceDescriptor.idProduct) {
		case USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING3_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING3_JIS:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 3 Series"
			);

			szHidDescriptor = AmtPtp3DefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtp3DefaultHidDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING5_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING5_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 5/5A Series"
			);

			szHidDescriptor = AmtPtp5DefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtp5DefaultHidDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING6_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING6_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 6/6A Series"
			);

			szHidDescriptor = AmtPtp6DefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtp6DefaultHidDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING7_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING7_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 7/7A Series"
			);

			szHidDescriptor = AmtPtp7aDefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtp7aDefaultHidDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING8_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING8_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_ISO:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 8 Series"
			);

			szHidDescriptor = AmtPtp8DefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtp8DefaultHidDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Request HID Report Descriptor for Apple Magic Trackpad 2 Family"
			);

			szHidDescriptor = AmtPtpMt2DefaultHidDescriptor.bLength;
			pSelectedHidDescriptor = &AmtPtpMt2DefaultHidDescriptor;
			break;
		}
	}

	if (pSelectedHidDescriptor != NULL && szHidDescriptor > 0) {
		status = WdfMemoryCopyFromBuffer(
			RequestMemory,
			0,
			(PVOID) pSelectedHidDescriptor,
			szHidDescriptor
		);

		if (!NT_SUCCESS(status)) {
			TraceEvents(
				TRACE_LEVEL_ERROR,
				TRACE_DRIVER,
				"%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!",
				status
			);
			goto exit;
		}

		WdfRequestSetInformation(
			Request,
			szHidDescriptor
		);
	}
	else {
		TraceEvents(
			TRACE_LEVEL_WARNING,
			TRACE_DRIVER,
			"%!FUNC! Device HID registry is not found"
		);
		TraceLoggingWrite(
			g_hAmtPtpDeviceTraceProvider,
			EVENT_DEVICE_IDENTIFICATION,
			TraceLoggingString("AmtPtpGetHidDescriptor", "Routine"),
			TraceLoggingUInt16(pContext->DeviceDescriptor.idProduct, "idProduct"),
			TraceLoggingString(EVENT_DEVICE_ID_SUBTYPE_HIDREG_NOTFOUND, EVENT_DRIVER_FUNC_SUBTYPE)
		);
		status = STATUS_INVALID_DEVICE_STATE;
		goto exit;
	}

exit:
	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);
	return status;
}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpGetDeviceAttribs(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{

	NTSTATUS               status = STATUS_SUCCESS;
	PDEVICE_CONTEXT        pContext = DeviceGetContext(Device);
	PHID_DEVICE_ATTRIBUTES pDeviceAttributes = NULL;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = WdfRequestRetrieveOutputBuffer(
		Request, 
		sizeof(HID_DEVICE_ATTRIBUTES), 
		&pDeviceAttributes, 
		NULL
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", 
			status
		);
		goto exit;
	}

	pDeviceAttributes->Size          = sizeof(HID_DEVICE_ATTRIBUTES);
	pDeviceAttributes->ProductID     = pContext->DeviceDescriptor.idProduct;
	// Okay here's one thing: we cannot report the real ID here, otherwise there's will be some great conflict with the USB/BT driver.
	// Therefore Vendor ID is changed to a hardcoded number
	pDeviceAttributes->VendorID      = DEVICE_VID;
	pDeviceAttributes->VersionNumber = DEVICE_VERSION;
	
	WdfRequestSetInformation(
		Request, 
		sizeof(HID_DEVICE_ATTRIBUTES)
	);

exit:
	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);

	return status;
}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpGetReportDescriptor(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{
	
	NTSTATUS               status = STATUS_SUCCESS;
	PDEVICE_CONTEXT        pContext = DeviceGetContext(Device);
	size_t			       szHidDescriptor = 0;
	WDFMEMORY              RequestMemory;
	PHID_REPORT_DESCRIPTOR pSelectedHidDescriptor = NULL;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = WdfRequestRetrieveOutputMemory(
		Request, 
		&RequestMemory
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", 
			status
		);
		goto exit;
	}

	switch (pContext->DeviceDescriptor.idProduct) {
		case USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING3_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING3_JIS:
		{
			szHidDescriptor = AmtPtp3DefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtp3ReportDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING5_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING5_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS:
		{
			szHidDescriptor = AmtPtp5DefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtp5ReportDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING6_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING6_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS:
		{
			szHidDescriptor = AmtPtp6DefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtp6ReportDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING7_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING7_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS:
		{
			szHidDescriptor = AmtPtp7aDefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtp7aReportDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING8_ISO:
		case USB_DEVICE_ID_APPLE_WELLSPRING8_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_JIS:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI:
		case USB_DEVICE_ID_APPLE_WELLSPRING9_ISO:
		{
			szHidDescriptor = AmtPtp8DefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtp8ReportDescriptor;
			break;
		}
		case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2:
		{
			szHidDescriptor = AmtPtpMt2DefaultHidDescriptor.DescriptorList[0].wReportLength;
			pSelectedHidDescriptor = AmtPtpMt2ReportDescriptor;
			break;
		}
	}

	if (pSelectedHidDescriptor != NULL && szHidDescriptor > 0) {
		status = WdfMemoryCopyFromBuffer(
			RequestMemory,
			0,
			(PVOID) pSelectedHidDescriptor,
			szHidDescriptor
		);

		if (!NT_SUCCESS(status)) {
			TraceEvents(
				TRACE_LEVEL_ERROR,
				TRACE_DRIVER,
				"%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!",
				status
			);
			return status;
		}

		WdfRequestSetInformation(
			Request,
			szHidDescriptor
		);
	}
	else if (szHidDescriptor == 0) {
		status = STATUS_INVALID_DEVICE_STATE;
		TraceEvents(
			TRACE_LEVEL_WARNING,
			TRACE_DRIVER,
			"%!FUNC! Device HID report length is zero"
		);
		goto exit;
	}
	else {
		TraceEvents(
			TRACE_LEVEL_WARNING,
			TRACE_DRIVER,
			"%!FUNC! Device HID registry is not found"
		);
		TraceLoggingWrite(
			g_hAmtPtpDeviceTraceProvider,
			EVENT_DEVICE_IDENTIFICATION,
			TraceLoggingString("AmtPtpGetReportDescriptor", "Routine"),
			TraceLoggingUInt16(pContext->DeviceDescriptor.idProduct, "idProduct"),
			TraceLoggingString(EVENT_DEVICE_ID_SUBTYPE_HIDREG_NOTFOUND, EVENT_DRIVER_FUNC_SUBTYPE)
		);
		status = STATUS_INVALID_DEVICE_STATE;
		goto exit;
	}

exit:
	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);
	return status;
}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpGetStrings(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{
	
	NTSTATUS               status = STATUS_SUCCESS;
	PDEVICE_CONTEXT        pContext = DeviceGetContext(Device);
	void                   *pStringBuffer = NULL;
	WDFMEMORY              memHandle;
	USHORT                 wcharCount;
	size_t                 actualSize;
	UCHAR                  strIndex;

	ULONG                  inputValue;
	WDFMEMORY              inputMemory;
	size_t                 inputBufferLength;
	PVOID                  inputBuffer;
	ULONG                  languageId, stringId;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = WdfRequestRetrieveInputMemory(
		Request, 
		&inputMemory
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_INFORMATION,
			TRACE_DRIVER, 
			"%!FUNC! WdfRequestRetrieveInputMemory failed with status %!STATUS!",
			status
		);
		return status;
	}

	inputBuffer = WdfMemoryGetBuffer(
		inputMemory, 
		&inputBufferLength
	);

	//
	// make sure buffer is big enough.
	//
	if (inputBufferLength < sizeof(ULONG)) {
		status = STATUS_INVALID_BUFFER_SIZE;
		TraceEvents(
			TRACE_LEVEL_INFORMATION, 
			TRACE_DRIVER, 
			"%!FUNC! GetStringId: invalid input buffer. size %d, expect %d",
			(int)inputBufferLength, 
			(int) sizeof(ULONG)
		);
		return status;
	}

	inputValue = (*(PULONG)inputBuffer);
	stringId = (inputValue & 0x0ffff);
	languageId = (inputValue >> 16);

	// Get actual string from USB device
	switch (stringId)
	{
		case HID_STRING_ID_IMANUFACTURER:
			strIndex = pContext->DeviceDescriptor.iManufacturer;
			break;
		case HID_STRING_ID_IPRODUCT:
			strIndex = pContext->DeviceDescriptor.iProduct;
			break;
		case HID_STRING_ID_ISERIALNUMBER:
			strIndex = pContext->DeviceDescriptor.iSerialNumber;
			break;
		default:
			TraceEvents(
				TRACE_LEVEL_WARNING, 
				TRACE_DRIVER, 
				"%!FUNC! gets invalid string type"
			);
			return status;
	}

	status = WdfUsbTargetDeviceAllocAndQueryString(
		pContext->UsbDevice, 
		WDF_NO_OBJECT_ATTRIBUTES, 
		&memHandle, 
		&wcharCount, 
		strIndex, 
		(USHORT) languageId
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, "%!FUNC! WdfUsbTargetDeviceAllocAndQueryString failed with %!STATUS!", 
			status
		);
		return status;
	}

	status = WdfRequestRetrieveOutputBuffer(
		Request, 
		wcharCount * sizeof(WCHAR), 
		&pStringBuffer, 
		&actualSize
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", 
			status
		);
		return status;
	}

	WdfMemoryCopyToBuffer(
		memHandle,
		0, 
		&pStringBuffer, 
		actualSize
	);

	WdfRequestSetInformation(
		Request, 
		actualSize
	);

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);
	return status;

}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
RequestGetHidXferPacketToReadFromDevice(
	_In_  WDFREQUEST        Request,
	_Out_ HID_XFER_PACKET  *Packet
)
{

	//
	// Driver need to write to the output buffer (so that App can read from it)
	//
	//   Report Buffer: Output Buffer
	//   Report Id    : Input Buffer
	//

	NTSTATUS                status;
	WDFMEMORY               inputMemory;
	WDFMEMORY               outputMemory;
	size_t                  inputBufferLength;
	size_t                  outputBufferLength;
	PVOID                   inputBuffer;
	PVOID                   outputBuffer;

	//
	// Get report Id from input buffer
	//
	status = WdfRequestRetrieveInputMemory(
		Request, 
		&inputMemory
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! WdfRequestRetrieveInputMemory failed with %!STATUS!", 
			status
		);
		return status;
	}
	inputBuffer = WdfMemoryGetBuffer(
		inputMemory, 
		&inputBufferLength
	);

	if (inputBufferLength < sizeof(UCHAR)) {
		status = STATUS_INVALID_BUFFER_SIZE;
		TraceEvents(
			TRACE_LEVEL_ERROR,
			TRACE_DRIVER,
			"%!FUNC! WdfRequestRetrieveInputMemory: invalid input buffer. size %d, expect %d",
			(int) inputBufferLength, 
			(int) sizeof(UCHAR)
		);
		return status;
	}

	Packet->reportId = *(PUCHAR) inputBuffer;

	//
	// Get report buffer from output buffer
	//
	status = WdfRequestRetrieveOutputMemory(
		Request, 
		&outputMemory
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR,
			TRACE_DRIVER,
			"%!FUNC! WdfRequestRetrieveOutputMemory failed with %!STATUS!",
			status
		);
		return status;
	}

	outputBuffer = WdfMemoryGetBuffer(
		outputMemory, 
		&outputBufferLength
	);

	Packet->reportBuffer = (PUCHAR) outputBuffer;
	Packet->reportBufferLen = (ULONG) outputBufferLength;

	return status;

}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
RequestGetHidXferPacketToWriteToDevice(
	_In_  WDFREQUEST        Request,
	_Out_ HID_XFER_PACKET  *Packet
)
{

	//
	// Driver need to read from the input buffer (which was written by App)
	//
	//   Report Buffer: Input Buffer
	//   Report Id    : Output Buffer Length
	//
	// Note that the report id is not stored inside the output buffer, as the
	// driver has no read-access right to the output buffer, and trying to read
	// from the buffer will cause an access violation error.
	//
	// The workaround is to store the report id in the OutputBufferLength field,
	// to which the driver does have read-access right.
	//

	NTSTATUS                status;
	WDFMEMORY               inputMemory;
	WDFMEMORY               outputMemory;
	size_t                  inputBufferLength;
	size_t                  outputBufferLength;
	PVOID                   inputBuffer;

	//
	// Get report Id from output buffer length
	//
	status = WdfRequestRetrieveOutputMemory(
		Request, 
		&outputMemory
	);
	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR,
			TRACE_DRIVER,
			"%!FUNC! WdfRequestRetrieveOutputMemory failed with %!STATUS!",
			status
		);
		return status;
	}
	WdfMemoryGetBuffer(
		outputMemory, 
		&outputBufferLength
	);
	Packet->reportId = (UCHAR) outputBufferLength;

	//
	// Get report buffer from input buffer
	//
	status = WdfRequestRetrieveInputMemory(
		Request, 
		&inputMemory
	);
	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR,
			TRACE_DRIVER,
			"%!FUNC! WdfRequestRetrieveInputMemory failed with %!STATUS!",
			status
		);
		return status;
	}
	inputBuffer = WdfMemoryGetBuffer(
		inputMemory, 
		&inputBufferLength
	);

	Packet->reportBuffer = (PUCHAR) inputBuffer;
	Packet->reportBufferLen = (ULONG) inputBufferLength;

	return status;

}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpReportFeatures(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{

	NTSTATUS status;
	PDEVICE_CONTEXT deviceContext;
	HID_XFER_PACKET packet;
	size_t reportSize;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = STATUS_SUCCESS;
	deviceContext = DeviceGetContext(Device);

	status = RequestGetHidXferPacketToReadFromDevice(
		Request, 
		&packet
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! RequestGetHidXferPacketToReadFromDevice failed with status %!STATUS!", 
			status
		);
		goto exit;
	}

	switch (packet.reportId)
	{
		case REPORTID_DEVICE_CAPS:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_DEVICE_CAPS is requested"
			);

			// Size sanity check
			reportSize = sizeof(PTP_DEVICE_CAPS_FEATURE_REPORT);
			if (packet.reportBufferLen < reportSize) {
				status = STATUS_INVALID_BUFFER_SIZE;
				TraceEvents(
					TRACE_LEVEL_ERROR, 
					TRACE_DRIVER, 
					"%!FUNC! Report buffer is too small"
				);
				goto exit;
			}

			PPTP_DEVICE_CAPS_FEATURE_REPORT capsReport = (PPTP_DEVICE_CAPS_FEATURE_REPORT) packet.reportBuffer;

			capsReport->MaximumContactPoints = PTP_MAX_CONTACT_POINTS;
			capsReport->ButtonType = PTP_BUTTON_TYPE_CLICK_PAD;
			capsReport->ReportID = REPORTID_DEVICE_CAPS;

			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_DEVICE_CAPS has maximum contact points of %d", 
				capsReport->MaximumContactPoints
			);
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_DEVICE_CAPS has touchpad type %d", 
				capsReport->ButtonType
			);
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_DEVICE_CAPS is fulfilled"
			);

			WdfRequestSetInformation(
				Request, 
				reportSize
			);
			break;
		}
		case REPORTID_PTPHQA:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_PTPHQA is requested"
			);

			// Size sanity check
			reportSize = sizeof(PTP_DEVICE_HQA_CERTIFICATION_REPORT);
			if (packet.reportBufferLen < reportSize) {
				status = STATUS_INVALID_BUFFER_SIZE;
				TraceEvents(
					TRACE_LEVEL_ERROR, 
					TRACE_DRIVER, 
					"%!FUNC! Report buffer is too small."
				);
				goto exit;
			}

			PPTP_DEVICE_HQA_CERTIFICATION_REPORT certReport = (PPTP_DEVICE_HQA_CERTIFICATION_REPORT) packet.reportBuffer;

			*certReport->CertificationBlob = DEFAULT_PTP_HQA_BLOB;
			certReport->ReportID = REPORTID_PTPHQA;

			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_PTPHQA is fulfilled"
			);

			WdfRequestSetInformation(
				Request, 
				reportSize
			);
			break;
		}
		case REPORTID_UMAPP_CONF:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Report REPORTID_UMAPP_CONF is requested"
			);

			// Size sanity check
			reportSize = sizeof(PTP_USERMODEAPP_CONF_REPORT);
			if (packet.reportBufferLen < reportSize) {
				status = STATUS_INVALID_BUFFER_SIZE;
				TraceEvents(
					TRACE_LEVEL_ERROR,
					TRACE_DRIVER,
					"%!FUNC! Report buffer is too small."
				);
				goto exit;
			}

			PPTP_USERMODEAPP_CONF_REPORT confReport = (PPTP_USERMODEAPP_CONF_REPORT)packet.reportBuffer;
			
			confReport->ReportID = REPORTID_UMAPP_CONF;
			confReport->MultipleContactSizeQualificationLevel = deviceContext->MuContactSizeQualLevel;
			confReport->SingleContactSizeQualificationLevel = deviceContext->SgContactSizeQualLevel;
			confReport->PressureQualificationLevel = deviceContext->PressureQualLevel;

			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Report REPORTID_UMAPP_CONF is fulfilled"
			);

			WdfRequestSetInformation(
				Request,
				reportSize
			);
			break;
		}
		default:
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Unsupported type %d is requested", 
				packet.reportId
			);

			status = STATUS_NOT_SUPPORTED;
			goto exit;
	}
exit:
	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);
	return status;

}

_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
AmtPtpSetFeatures(
	_In_ WDFDEVICE Device,
	_In_ WDFREQUEST Request
)
{

	NTSTATUS        status;
	HID_XFER_PACKET packet;
	PDEVICE_CONTEXT deviceContext;

	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Entry"
	);

	status = STATUS_SUCCESS;
	deviceContext = DeviceGetContext(Device);

	status = RequestGetHidXferPacketToWriteToDevice(
		Request, 
		&packet
	);

	if (!NT_SUCCESS(status)) {
		TraceEvents(
			TRACE_LEVEL_ERROR, 
			TRACE_DRIVER, 
			"%!FUNC! RequestGetHidXferPacketToWriteToDevice failed with status %!STATUS!", 
			status
		);
		goto exit;
	}

	switch (packet.reportId)
	{
		case REPORTID_REPORTMODE:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_REPORTMODE is requested"
			);

			PPTP_DEVICE_INPUT_MODE_REPORT devInputMode = (PPTP_DEVICE_INPUT_MODE_REPORT) packet.reportBuffer;

			// Get current WellSpring mode
			BOOL bWellspringMode = FALSE;
			status = AmtPtpGetWellspringMode(
				deviceContext,
				&bWellspringMode
			);

			if (!NT_SUCCESS(status)) {

				TraceEvents(
					TRACE_LEVEL_ERROR,
					TRACE_DRIVER,
					"%!FUNC! -> AmtPtpGetWellspringMode failed with status %!STATUS!",
					status
				);
				goto exit;

			}

			switch (devInputMode->Mode)
			{
				case PTP_COLLECTION_MOUSE:
				{

					TraceEvents(
						TRACE_LEVEL_INFORMATION,
						TRACE_DRIVER,
						"%!FUNC! Report REPORTID_REPORTMODE requested Mouse Input"
					);

					if (bWellspringMode) {

						status = AmtPtpSetWellspringMode(
							deviceContext,
							FALSE
						);

						if (!NT_SUCCESS(status)) {
							TraceEvents(
								TRACE_LEVEL_ERROR,
								TRACE_DRIVER,
								"%!FUNC! -> AmtPtpSetWellspringMode failed with status %!STATUS!",
								status
							);
							goto exit;
						}

					}
				
					break;

				}
				case PTP_COLLECTION_WINDOWS:
				{

					TraceEvents(
						TRACE_LEVEL_INFORMATION,
						TRACE_DRIVER,
						"%!FUNC! Report REPORTID_REPORTMODE requested Windows PTP Input"
					);

					if (!bWellspringMode) {
						
						status = AmtPtpSetWellspringMode(
							deviceContext,
							TRUE
						);

						if (!NT_SUCCESS(status)) {
							TraceEvents(
								TRACE_LEVEL_ERROR,
								TRACE_DRIVER,
								"%!FUNC! -> AmtPtpSetWellspringMode failed with status %!STATUS!",
								status
							);
							goto exit;
						}

					}

					break;

				}
			}

			WdfRequestSetInformation(
				Request, 
				sizeof(PTP_DEVICE_INPUT_MODE_REPORT)
			);

			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_REPORTMODE is fulfilled"
			);
			break;
		}
		case REPORTID_FUNCSWITCH:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_FUNCSWITCH is requested"
			);
			PPTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT secInput = (PPTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT) packet.reportBuffer;

			deviceContext->IsButtonReportOn = secInput->ButtonReport;
			deviceContext->IsSurfaceReportOn = secInput->SurfaceReport;

			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_FUNCSWITCH requested Button = %d, Surface = %d",
				secInput->ButtonReport, 
				secInput->SurfaceReport
			);

			WdfRequestSetInformation(
				Request,
				sizeof(PTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT)
			);

			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER, 
				"%!FUNC! Report REPORTID_FUNCSWITCH is fulfilled"
			);
			break;
		}
		case REPORTID_UMAPP_CONF:
		{
			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Report REPORTID_UMAPP_CONF is requested"
			);
			PPTP_USERMODEAPP_CONF_REPORT umConfInput = (PPTP_USERMODEAPP_CONF_REPORT) packet.reportBuffer;

			// Set value
			deviceContext->SgContactSizeQualLevel = umConfInput->SingleContactSizeQualificationLevel;
			deviceContext->MuContactSizeQualLevel = umConfInput->MultipleContactSizeQualificationLevel;
			deviceContext->PressureQualLevel = umConfInput->PressureQualificationLevel;

			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Report REPORTID_UMAPP_CONF requested PressureQual = %d, SgSize = %d, MuSize = %d",
				umConfInput->PressureQualificationLevel,
				umConfInput->SingleContactSizeQualificationLevel,
				umConfInput->MultipleContactSizeQualificationLevel
			);

			// Report back
			WdfRequestSetInformation(
				Request,
				sizeof(PTP_USERMODEAPP_CONF_REPORT)
			);

			TraceEvents(
				TRACE_LEVEL_INFORMATION,
				TRACE_DRIVER,
				"%!FUNC! Report REPORTID_UMAPP_CONF is fulfilled"
			);

			break;
		}
		default:
			TraceEvents(
				TRACE_LEVEL_INFORMATION, 
				TRACE_DRIVER, 
				"%!FUNC! Unsupported type %d is requested",
				packet.reportId
			);
			status = STATUS_NOT_SUPPORTED;
			goto exit;
	}

exit:
	TraceEvents(
		TRACE_LEVEL_INFORMATION, 
		TRACE_DRIVER, 
		"%!FUNC! Exit"
	);
	return STATUS_SUCCESS;

}