/*
 * Cleans up resources and set exception that will get thrown upon return to java layer.
 */
static jstring clean_throw_exp_usbenumeration(JNIEnv *env, int task, int subtask, DWORD error_code,
	const char *expmsg, struct jstrarray_list *list, struct hiddev_instance_list *hiddevinst_list,
	HDEVINFO *usb_dev_info_set, HDEVINFO *hid_dev_info_set) {

	(*env)->ExceptionClear(env);

	if (task == 1) {
		SetupDiDestroyDeviceInfoList(*hid_dev_info_set);
		free_hiddev_instance_list(hiddevinst_list);
	}else if (task == 2) {
		free_hiddev_instance_list(hiddevinst_list);
	}else if (task == 3) {
		free_jstrarraylist(list);
		free_hiddev_instance_list(hiddevinst_list);
		SetupDiDestroyDeviceInfoList(*usb_dev_info_set);
	}

	if (subtask == 1) {
		throw_serialcom_exception(env, 3, 0, expmsg);
	}else if (subtask == 2) {
		throw_serialcom_exception(env, 4, error_code, NULL);
	}else {
	}

	return NULL;
}
/*
 * Find the address and IRQ number associated with the given handle of serial port.
 */
jstring find_address_irq_for_given_com_port(JNIEnv *env, jlong fd) {

	char serial_info[256];
	jstring addressIRQInfo = NULL;
	int ret = 0;
	struct serial_struct port_info;

	memset(serial_info, '\0', 256);

	port_info.reserved_char[0] = 0;
	errno = 0;
	ret = ioctl(fd, TIOCGSERIAL, &port_info);
	if(ret < 0) {
		throw_serialcom_exception(env, 1, errno, NULL);
		return NULL;
	}

	snprintf(serial_info, 256, "Port: 0x%.4x, IRQ: %d", port_info.port, port_info.irq);
	addressIRQInfo = (*env)->NewStringUTF(env, serial_info);
	if((addressIRQInfo == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		(*env)->ExceptionClear(env);
		throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
		return NULL;
	}

	return addressIRQInfo;
}
/*
 * Set the latency timer value for ftdi devices to fine tune performance.
 */
jint set_latency_timer_value(JNIEnv *env, jstring comPortName, jbyte timerValue) {

	const char *com_port_to_match = NULL;

	com_port_to_match = (*env)->GetStringUTFChars(env, comPortName, NULL);
	if ((com_port_to_match == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return -1;
	}

	(*env)->ReleaseStringUTFChars(env, comPortName, com_port_to_match);

	/* reaching here means given com port does not represent ftdi device, throw exception */
	throw_serialcom_exception(env, 3, 0, E_NOTFTDIPORT);
	return -1;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    readEEPROM
 * Signature: (I)I
 *
 * @return eeprom value read on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_readEEPROM(JNIEnv *env, 
	jobject obj, jint uiEEPAddress) {

	int value = 0;

	value = scm_ReadEEPROM(uiEEPAddress);
	if (value < 0) {
		if (value == -3) {
			throw_serialcom_exception(env, 3, 0, "E_WRONG_ADDRESS");
		}else if (value == -4) {
			throw_serialcom_exception(env, 3, 0, "E_CANNOT_SEND_DATA");
		}else {
			throw_serialcom_exception(env, 3, 0, "ReadEEPROM() failed to read eeprom value !");
		}
		return -1;
	}

	return (jint)value;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    writeEEPROM
 * Signature: (IS)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_writeEEPROM(JNIEnv *env, 
	jobject obj, jint uiEEPAddress, jshort ucValue) {

	int ret = 0;

	ret = scm_WriteEEPROM(uiEEPAddress, (unsigned char)ucValue);
	if (ret < 0) {
		if (ret == -3) {
			throw_serialcom_exception(env, 3, 0, "E_WRONG_ADDRESS");
		}else if (ret == -4) {
			throw_serialcom_exception(env, 3, 0, "E_CANNOT_SEND_DATA");
		}else {
			throw_serialcom_exception(env, 3, 0, "WriteEEPROM() failed to write eeprom value !");
		}
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    selectDevice
 * Signature: (I)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_selectDevice(JNIEnv *env, 
	jobject obj, jint uiDeviceNumber) {

	int ret = 0;

	ret = scm_SelectDevice(uiDeviceNumber);
	if (ret = 0) {
		if (ret == -1) {
			throw_serialcom_exception(env, 3, 0, "E_WRONG_DEVICE_ID");
		}else if (ret == -2) {
			throw_serialcom_exception(env, 3, 0, "E_INACTIVE_DEVICE");
		}else {
			throw_serialcom_exception(env, 3, 0, "SelectDevice() failed !");
		}
		
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    clearPin
 * Signature: (I)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_clearPin(JNIEnv *env, 
	jobject obj, jint pinNumber) {

	BOOL ret = FALSE;
	ret = scm_ClearPin(pinNumber);
	if (ret = FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    readPinValue
 * Signature: (I)I
 *
 * @return pin value read on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_readPinValue(JNIEnv *env, 
	jobject obj, jint pinNumber) {

	int ret = 0;
	ret = scm_ReadPinValue(pinNumber);
	if (ret = 0x8000) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return (jint)ret;
}
/*
 * @return number of bytes read if function succeeds otherwise -1 if an error occurs.
 * @throws SerialComException if any JNI function, system call or C function fails.
 */
jint mac_get_feature_report(JNIEnv *env, jlong fd, jbyte reportID, jbyteArray report, jint length) {
	IOReturn ret = -1;
	int num_bytes_to_read = 0;
	int report_id = -1;
	jbyte* buffer = NULL;

	if(reportID < 0) {
		buffer = (jbyte *) calloc(length, sizeof(unsigned char));
		if(buffer == NULL) {
			throw_serialcom_exception(env, 3, 0, E_CALLOCSTR);
			return -1;
		}
		(*env)->GetByteArrayRegion(env, report, 0, length, &buffer[0]);
		num_bytes_to_read = length;
		report_id = 0x00;
	}else {
		buffer = (jbyte *) calloc((length + 1), sizeof(unsigned char));
		if(buffer == NULL) {
			throw_serialcom_exception(env, 3, 0, E_CALLOCSTR);
			return -1;
		}
		buffer[0] = reportID;
		(*env)->GetByteArrayRegion(env, report, 0, length, &buffer[1]);
		num_bytes_to_read = length + 1;
		report_id = reportID;
	}
	if((*env)->ExceptionOccurred(env) != NULL) {
		throw_serialcom_exception(env, 3, 0, E_GETBYTEARRREGIONSTR);
		return -1;
	}

	/* after completion num_bytes_to_read will contain number of bytes read. */
	ret = IOHIDDeviceGetReport(fd, kIOHIDReportTypeFeature, report_id, buffer, &num_bytes_to_read);
	if(ret != kIOReturnSuccess) {
		/* to error throw_serialcom_exception(env, 1, errno, NULL);*/
		return -1;
	}

	return num_bytes_to_read;
}
/*
 * @return number of bytes read if function succeeds otherwise -1 if an error occurs.
 * @throws SerialComException if any JNI function, system call or C function fails.
 */
jint linux_get_feature_report(JNIEnv *env, jlong fd, jbyte reportID, jbyteArray report, jint length) {
	int ret = -1;
	jbyte* buffer = NULL;;

	/* allocate and clear buffer */
	buffer = (jbyte *) calloc((length), sizeof(unsigned char));
	if(buffer == NULL) {
		throw_serialcom_exception(env, 3, 0, E_CALLOCSTR);
		return -1;
	}

	if(reportID < 0) {
		/* set 0x00 as 1st byte if device does not support report ID */
		buffer[0] = 0x00;
	}else {
		/* The first byte of SFEATURE and GFEATURE is the report number */
		buffer[0] = reportID;
	}

	errno = 0;
	/* this ioctl returns number of bytes read from the device */
	ret = ioctl(fd, HIDIOCGFEATURE(length), buffer);
	if(ret < 0) {
		free(buffer);
		throw_serialcom_exception(env, 1, errno, NULL);
		return -1;
	}

	/* copy data from native buffer to Java buffer */
	(*env)->SetByteArrayRegion(env, report, 0, ret, buffer);
	if((*env)->ExceptionOccurred(env)) {
		free(buffer);
		throw_serialcom_exception(env, 3, 0, E_SETBYTEARRREGIONSTR);
		return -1;
	}

	free(buffer);
	return ret;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    writePort
 * Signature: (I)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_writePort(JNIEnv *env, 
	jobject obj, jint portValue) {

	BOOL ret = FALSE;

	ret = scm_WritePort(portValue);
	if (ret = FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    configureIoDefaultOutput
 * Signature: (SS)I
 *
 * 1 - GPIO configured as input
 * 0 - GPIO configured as output
 * MSB  -   -   -   -   -   -  LSB
 * GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_configureIoDefaultOutput(JNIEnv *env, 
	jobject obj, jshort ioMap, jshort ucDefValue) {

	BOOL ret = FALSE;

	ret = scm_ConfigureIoDefaultOutput((unsigned char)ioMap, (unsigned char)ucDefValue);
	if (ret == FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    fnSetBaudRate
 * Signature: (J)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_fnSetBaudRate(JNIEnv *env, 
	jobject obj, jlong baudRateParam) {

	BOOL ret = FALSE;

	ret = scm_fnSetBaudRate((unsigned long)baudRateParam);
	if (ret == FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    readPortValue
 * Signature: ()I
 *
 * @return port value read on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_readPortValue(JNIEnv *env, 
	jobject obj) {

	int value = 0;

	value = scm_ReadPortValue();
	if (value = 0x8000) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return (jint)value;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    fnInvertUartPol
 * Signature: (I)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_fnInvertUartPol(JNIEnv *env, 
	jobject obj, jint onOff) {

	BOOL ret = FALSE;

	ret = scm_fnInvertUartPol(onOff);
	if (ret == FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    readPort
 * Signature: ()I
 *
 * @return port value read on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_readPort(JNIEnv *env,
	jobject obj) {

	BOOL ret = FALSE;
	unsigned int value = 0;

	ret = scm_ReadPort(&value);
	if (ret = FALSE) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return (jint)value;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    getSelectedDeviceInfo
 * Signature: ()Ljava/lang/String;
 *
 * @return string returned by SelectedDeviceInfo() on success or NULL if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jstring JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_getSelectedDeviceInfo(JNIEnv *env, 
	jobject obj) {

	char buffer[256];
	jstring info = NULL;

	memset(buffer, '\0', 256);
	scm_GetSelectedDeviceInfo(buffer);
	info = (*env)->NewStringUTF(env, buffer);
	if ((info == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
		return NULL;
	}

	return info;
}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    configureMCP2200
 * Signature: (BJIIZZZZ)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_configureMCP2200(JNIEnv *env, 
	jobject obj, jbyte ioMap, jlong baudRateParam, jint rxLEDMode, jint txLEDMode, jboolean flow, jboolean uload, 
	jboolean sspnd, jboolean invert) {

	BOOL ret = FALSE;
	BOOL flowval = FALSE;
	BOOL uloadval = FALSE;
	BOOL sspndval = FALSE;
	BOOL invertval = FALSE;

	if (flow == JNI_TRUE) {
		flowval = TRUE;
	}else {
		flowval = FALSE;
	}
	if (uload == JNI_TRUE) {
		uloadval = TRUE;
	}else {
		uloadval = FALSE;
	}
	if (sspnd == JNI_TRUE) {
		sspndval = TRUE;
	}else {
		sspndval = FALSE;
	}
	if (invert == JNI_TRUE) {
		invertval = TRUE;
	}else {
		invertval = FALSE;
	}

	ret = scm_ConfigureMCP2200(ioMap, (unsigned long)baudRateParam, rxLEDMode, txLEDMode, flowval, uloadval, sspndval, invertval);
	if (ret = FALSE) {
		throw_serialcom_exception(env, 3, 0, "ConfigureMCP2200() returned FALSE !");
		return -1;
	}

	return 0;
}
/*
 * Find the name of the driver which is currently associated with the given HID device.
 *
 * A HID device can be on usb, bluetooth or pseudo. For Linux we walk down the
 * sysfs tree until a driver is found for the given device node.
 */
jstring linux_find_driver_for_given_hiddevice(JNIEnv *env, jstring hidDevNode) {

	const char* hid_name_to_match = NULL;
	jstring driver_name = NULL;
	int check_for_parent = -1;
	struct udev *udev_ctx;
	struct udev_enumerate *enumerator;
	struct udev_list_entry *devices, *dev_list_entry;
	const char *prop_val_driver_name;
	const char *device_node;
	const char *path;
	struct udev_device *udev_device;
	struct udev_device *parent_device;

	hid_name_to_match = (*env)->GetStringUTFChars(env, hidDevNode, NULL);
	if((hid_name_to_match == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return NULL;
	}

	udev_ctx = udev_new();
	enumerator = udev_enumerate_new(udev_ctx);
	udev_enumerate_add_match_subsystem(enumerator, "hidraw");
	udev_enumerate_scan_devices(enumerator);
	devices = udev_enumerate_get_list_entry(enumerator);

	udev_list_entry_foreach(dev_list_entry, devices) {

		/* from the sysfs filename create a udev_device object representing it. */
		path = udev_list_entry_get_name(dev_list_entry);
		udev_device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), path);
		if(udev_device == NULL) {
			continue;
		}

		/* get the device node for this udev device. */
		device_node = udev_device_get_devnode(udev_device);

		/* If the device node name matches what we are looking for get driver for it.
		 * if we fail to get driver name than return empty string (prop_val_driver_name
		 * will be NULL when control reaches at the end of this function). */
		if(device_node != NULL) {
			if(strcmp(hid_name_to_match, device_node) == 0) {
				prop_val_driver_name = udev_device_get_driver(udev_device);
				if(prop_val_driver_name != NULL) {
					driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
					if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
						(*env)->ExceptionClear(env);
						udev_device_unref(udev_device);
						udev_enumerate_unref(enumerator);
						udev_unref(udev_ctx);
						(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
						throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
						return NULL;
					}
					check_for_parent = -1;
				}else {
					check_for_parent = 1;
				}

				if(check_for_parent == 1) {
					parent_device = udev_device_get_parent(udev_device);
					if(parent_device != NULL) {
						prop_val_driver_name = udev_device_get_driver(parent_device);
						if(prop_val_driver_name != NULL) {
							driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
							if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
								(*env)->ExceptionClear(env);
								udev_device_unref(udev_device);
								udev_enumerate_unref(enumerator);
								udev_unref(udev_ctx);
								(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
								throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
								return NULL;
							}else {
								/* if both the parent and driver found, return driver name to caller. */
								check_for_parent = -1;
							}
						}else {
							/* if the parent is found but driver not found then analyze next parent device. */
							check_for_parent = 1;
						}
					}else {
						/* if the parent does not exist, make no more attempts to analyze parent devices further down the tree. */
						check_for_parent = -1;
					}
				}

				if(check_for_parent == 1) {
					parent_device = udev_device_get_parent(parent_device );
					if(parent_device != NULL) {
						prop_val_driver_name = udev_device_get_driver(parent_device);
						if(prop_val_driver_name != NULL) {
							driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
							if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
								(*env)->ExceptionClear(env);
								udev_device_unref(udev_device);
								udev_enumerate_unref(enumerator);
								udev_unref(udev_ctx);
								(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
								throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
								return NULL;
							}else {
								check_for_parent = -1;
							}
						}else {
							check_for_parent = 1;
						}
					}else {
						check_for_parent = -1;
					}
				}

				if(check_for_parent == 1) {
					parent_device = udev_device_get_parent(parent_device);
					if(parent_device != NULL) {
						prop_val_driver_name = udev_device_get_driver(parent_device);
						if(prop_val_driver_name != NULL) {
							driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
							if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
								(*env)->ExceptionClear(env);
								udev_device_unref(udev_device);
								udev_enumerate_unref(enumerator);
								udev_unref(udev_ctx);
								(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
								throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
								return NULL;
							}else {
								check_for_parent = -1;
							}
						}else {
							check_for_parent = 1;
						}
					}else {
						check_for_parent = -1;
					}
				}

				if(check_for_parent == 1) {
					parent_device = udev_device_get_parent(udev_device);
					if(parent_device != NULL) {
						prop_val_driver_name = udev_device_get_driver(parent_device );
						if(prop_val_driver_name != NULL) {
							driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
							if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
								(*env)->ExceptionClear(env);
								udev_device_unref(udev_device);
								udev_enumerate_unref(enumerator);
								udev_unref(udev_ctx);
								(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
								throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
								return NULL;
							}else {
								check_for_parent = -1;
							}
						}else {
							check_for_parent = 1;
						}
					}else {
						check_for_parent = -1;
					}
				}

				if(check_for_parent == 1) {
					parent_device = udev_device_get_parent(parent_device);
					if(parent_device != NULL) {
						prop_val_driver_name = udev_device_get_driver(parent_device);
						if(prop_val_driver_name != NULL) {
							driver_name = (*env)->NewStringUTF(env, prop_val_driver_name);
							if((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
								(*env)->ExceptionClear(env);
								udev_device_unref(udev_device);
								udev_enumerate_unref(enumerator);
								udev_unref(udev_ctx);
								(*env)->ReleaseStringUTFChars(env, hidDevNode, hid_name_to_match);
								throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
								return NULL;
							}
						}
					}
				}

				/* whether prop_val_driver_name is NULL (return empty string) or not
				 * (return driver name found), no more iteration is needed. */
				udev_device_unref(udev_device);
				break;
			}
		}

		/* released only after desired property value has been saved to some
		 * other memory region like one got from NewStringUTF(). */
		udev_device_unref(udev_device);
	}
/*
 * Class:     com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge
 * Method:    loadAndLinkSimpleIODLL
 * Signature: (Ljava/lang/String;)I
 *
 * @return 0 on success or -1 if an error occurs.
 * @throws SerialComException if any JNI function, Win API call or C function fails.
 */
JNIEXPORT jint JNICALL Java_com_embeddedunveiled_serial_internal_SerialComMCHPSIOJNIBridge_loadAndLinkSimpleIODLL
(JNIEnv *env, jobject obj, jstring vendorLibraryWithAbsolutePath) {

	const jchar* vlib = NULL;
	FARPROC function_address = 0;

	throw_serialcom_exception(env, 4, 193, NULL);
	return -1;

	vlib = (*env)->GetStringChars(env, vendorLibraryWithAbsolutePath, JNI_FALSE);
	if ((vlib == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return -1;
	}

	/* load vendor supplied shared library */
	DLL_handle = LoadLibrary(vlib);
	if (DLL_handle == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	
	/* resolve references to all the exported functions */
	scm_InitMCP2200 = (void(__stdcall *)(unsigned int, unsigned int)) GetProcAddress(DLL_handle, "InitMCP2200");
	if (scm_InitMCP2200 == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_IsConnected = (BOOL(__stdcall *)(void)) GetProcAddress(DLL_handle, "IsConnected");
	if (scm_IsConnected == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ConfigureMCP2200 = (BOOL(__stdcall *)(unsigned char, unsigned long, unsigned int, unsigned int, BOOL, BOOL, BOOL, BOOL)) GetProcAddress(DLL_handle, "ConfigureMCP2200");
	if (scm_ConfigureMCP2200 == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_SetPin = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "SetPin");
	if (scm_SetPin == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ClearPin = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "ClearPin");
	if (scm_ClearPin == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ReadPinValue = (int(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "ReadPinValue");
	if (scm_ReadPinValue == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ReadPin = (BOOL(__stdcall *)(unsigned int, unsigned int *)) GetProcAddress(DLL_handle, "ReadPin");
	if (scm_ReadPin == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_WritePort = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "WritePort");
	if (scm_WritePort == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ReadPort = (BOOL(__stdcall *)(unsigned int *)) GetProcAddress(DLL_handle, "ReadPort");
	if (scm_ReadPort == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ReadPortValue = (int(__stdcall *)(void)) GetProcAddress(DLL_handle, "ReadPortValue");
	if (scm_ReadPortValue == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_SelectDevice = (int(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "SelectDevice");
	if (scm_SelectDevice == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_GetSelectedDevice = (int(__stdcall *)(void)) GetProcAddress(DLL_handle, "GetSelectedDevice");
	if (scm_GetSelectedDevice == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_GetNoOfDevices = (unsigned int(__stdcall *)(void)) GetProcAddress(DLL_handle, "GetNoOfDevices");
	if (scm_GetNoOfDevices == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_GetDeviceInfo = (BOOL(__stdcall *)(unsigned int, LPSTR)) GetProcAddress(DLL_handle, "GetDeviceInfo");
	if (scm_GetDeviceInfo == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_GetSelectedDeviceInfo = (int(__stdcall *)(LPSTR)) GetProcAddress(DLL_handle, "GetSelectedDeviceInfo");
	if (scm_GetSelectedDeviceInfo == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ReadEEPROM = (int(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "ReadEEPROM");
	if (scm_ReadEEPROM == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_WriteEEPROM = (int(__stdcall *)(unsigned int, unsigned char)) GetProcAddress(DLL_handle, "WriteEEPROM");
	if (scm_WriteEEPROM == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnRxLED = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnRxLED");
	if (scm_fnRxLED == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnTxLED = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnTxLED");
	if (scm_fnTxLED == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnHardwareFlowControl = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnHardwareFlowControl");
	if (scm_fnHardwareFlowControl == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnULoad = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnULoad");
	if (scm_fnULoad == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnSuspend = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnSuspend");
	if (scm_fnSuspend == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnInvertUartPol = (BOOL(__stdcall *)(unsigned int)) GetProcAddress(DLL_handle, "fnInvertUartPol");
	if (scm_fnInvertUartPol == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_fnSetBaudRate = (BOOL(__stdcall *)(unsigned long)) GetProcAddress(DLL_handle, "fnSetBaudRate");
	if (scm_fnSetBaudRate == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ConfigureIO = (BOOL(__stdcall *)(unsigned char)) GetProcAddress(DLL_handle, "ConfigureIO");
	if (scm_ConfigureIO == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}
	scm_ConfigureIoDefaultOutput = (BOOL(__stdcall *)(unsigned char, unsigned char)) GetProcAddress(DLL_handle, "ConfigureIoDefaultOutput");
	if (scm_ConfigureIoDefaultOutput == NULL) {
		throw_serialcom_exception(env, 4, GetLastError(), NULL);
		return -1;
	}

	return 0;
}
/*
 * Last pass to find driver name. Iterate over all the devices associated with "Ports" class.
 * One by one get their device ID and check if this device has COM port we are searching.
 * If matched get the driver name and return to caller. Note that not all devices might have
 * "PortName" registry associated with them, so ignore them.
 * 
 * Return 1 if found, 0 if not found, -1 if an error occurs (also throws exception in this case).
 */
int get_driver_com_port_others(JNIEnv *env, const jchar *port_name, TCHAR *driver_name) {

	int x = 0;
	BOOL ret = FALSE;
	CONFIGRET cmret = 0;
	LONG status = 0;
	DWORD error_code = 0;
	DWORD charbuffer_size = 0;
	DWORD com_member_index = 0;
	HDEVINFO com_dev_info_set;
	SP_DEVINFO_DATA com_dev_instance;

	/* size of these buffers is hardcoded in functions using them */
	TCHAR buffer[1024];
	TCHAR keybuf[1024];
	TCHAR charbuffer[128];
	char cmerror[256];

	/* get information set for all multi port serial adaptor devices matching the GUID */
	com_dev_info_set = SetupDiGetClassDevs(&GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR, NULL, NULL, DIGCF_ALLCLASSES);
	if (com_dev_info_set == INVALID_HANDLE_VALUE) {
		SetupDiDestroyDeviceInfoList(com_dev_info_set);
		throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
		return -1;
	}

	/* enumerate all devices in this information set */
	com_member_index = 0;
	while (1) {
		ZeroMemory(&com_dev_instance, sizeof(com_dev_instance));
		com_dev_instance.cbSize = sizeof(com_dev_instance);

		/* from information set, get device by index */
		ret = SetupDiEnumDeviceInfo(com_dev_info_set, com_member_index, &com_dev_instance);
		if (ret == FALSE) {
			error_code = GetLastError();
			if (error_code == ERROR_NO_MORE_ITEMS) {
				break;
			}else {
				SetupDiDestroyDeviceInfoList(com_dev_info_set);
				throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(error_code), NULL);
				return -1;
			}
		}

		cmret = CM_Get_Device_ID(com_dev_instance.DevInst, buffer, 1024, 0);
		if (cmret != CR_SUCCESS) {
			SetupDiDestroyDeviceInfoList(com_dev_info_set);
			_snprintf_s(cmerror, 256, 256, "CM_Get_Device_ID CR_xxxx error code : 0x%X\0", cmret);
			throw_serialcom_exception(env, 3, 0, cmerror);
			return -1;
		}

		/* get its COM port name/number for this device */
		memset(keybuf, '\0', 1024);
		_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s\\Device Parameters"), buffer);

		charbuffer_size = sizeof(charbuffer);
		memset(charbuffer, '\0', 128);
		/* ignore error as some devices might not have portname registry key */
		status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("PortName"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
		if (status == ERROR_SUCCESS) {
			/* match port name */
			ret = _tcsicmp(charbuffer, port_name);
			if (ret == 0) {
				/* get driver name */
				memset(keybuf, '\0', 1024);
				_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s"), buffer);

				/* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\XXXX\XXX\XXX\Service */
				charbuffer_size = sizeof(charbuffer);
				memset(charbuffer, '\0', 128);
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("Service"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(com_dev_info_set);
					throw_serialcom_exception(env, 4, GetLastError(), NULL);
					return -1;
				}

				/* populate array to be returned to caller */
				memset(driver_name, '\0', 128);
				for (x = 0; x < _tcslen(charbuffer); x++) {
					driver_name[x] = charbuffer[x];
				}

				/* clean up and return 1 to indicate driver found */
				SetupDiDestroyDeviceInfoList(com_dev_info_set);
				return 1;
			}
		}

		/* increment to get and examine the next device instance */
		com_member_index++;
	}

	/* reaching here means given COM port's driver not found */
	SetupDiDestroyDeviceInfoList(com_dev_info_set);
	return 0;
}
/*
 * Find the name of the driver which is currently associated with the given serial port.
 *
 * Typically, a driver service is a type of kernel-level filter driver implemented as a Windows service that 
 * enables applications to work with devices. Windows copies the .sys file to the %SystemRoot%\system32\drivers 
 * directory.
 * 
 * The GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR is not used for USB devices due to 2 reasons : 
 * (1) Some devices may not have 'upper-level device filter driver (for ex. serenum)' that is used in conjunction with 
 *     Serial (or a vendor-supplied function driver) to provide the functionality of a Plug and Play bus driver for an 
 *     RS-232 port.
 * (2) For composite devices CDC/ACM interface specific function driver is to be found.
 * 
 * Return driver name string if driver found, empty string if not found, NULL if an error occur.
 */
jstring find_driver_for_given_com_port(JNIEnv *env, jstring comPortName) {

	int x = 0;
	const jchar* port_name = NULL;
	jstring driverfound = NULL;
	TCHAR driver_name[128];

	/* extract com port name to match (as an array of Unicode characters) */
	port_name = (*env)->GetStringChars(env, comPortName, JNI_FALSE);
	if ((port_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return NULL;
	}

	/* first pass : loop over USB-UART device enteries */
	x = get_driver_com_port_usb(env, port_name, driver_name);
	if (x == 1) {
		driverfound = (*env)->NewString(env, driver_name, (jsize) _tcslen(driver_name));
		if ((driverfound == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
			return NULL;
		}
		return driverfound;
	}else if (x < 0) {
		return NULL;
	}else {
	}

	/* reaching here means given COM port does not belong to USB device.
	   second pass : loop over multiport serial adaptor device enteries */
	x = get_driver_com_port_multiportadaptor(env, port_name, driver_name);
	if (x == 1) {
		driverfound = (*env)->NewString(env, driver_name, (jsize) _tcslen(driver_name));
		if ((driverfound == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
			return NULL;
		}
		return driverfound;
	}else if (x < 0) {
		return NULL;
	}else {
	}

	/* reaching here means given COM port does not belong to USB device.
	   last pass : loop over other device entires */
	x = get_driver_com_port_others(env, port_name, driver_name);
	if (x == 1) {
		driverfound = (*env)->NewString(env, driver_name, (jsize) _tcslen(driver_name));
		if ((driverfound == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
			return NULL;
		}
		return driverfound;
	}else if (x < 0) {
		return NULL;
	}else {
	}

	/* reaching here means that the no driver was found for given COM port name.
	   return empty string */
	driverfound = (*env)->NewStringUTF(env, "");
	if ((driverfound == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		(*env)->ExceptionClear(env);
		throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
		return NULL;
	}
	
	return driverfound;
}
/*
 * The sequence of entries in array must match with what java layer expect (6 informations
 * per USB device). If a particular USB attribute is not set in descriptor or can not be
 * obtained "---" is placed in its place.
 *
 * Returns array of USB device's information found, empty array if no USB device is found,
 * NULL if an error occurs (additionally throws exception).
 */
jobjectArray list_usb_devices(JNIEnv *env, jint vendor_to_match) {

	int x = 0;
	int i = 0;
	int vid = 0;
	BOOL ret = FALSE;
	DWORD error_code = 0;
	DWORD size = 0;
	TCHAR *ptrend;
	DWORD usb_member_index = 0;
	HDEVINFO usb_dev_info_set;
	SP_DEVINFO_DATA usb_dev_instance;
	DEVPROPTYPE proptype;
	DWORD regproptype;
	TCHAR buffer[1024];
	TCHAR charbuffer[1024];

	struct jstrarray_list list = { 0 };
	jstring usb_dev_info;
	jclass strClass = NULL;
	jobjectArray usbDevicesFound = NULL;

	init_jstrarraylist(&list, 50);

	/* get information set for all usb devices matching the GUID */
	usb_dev_info_set = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
	if (usb_dev_info_set == INVALID_HANDLE_VALUE) {
		SetupDiDestroyDeviceInfoList(usb_dev_info_set);
		free_jstrarraylist(&list);
		throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
		return NULL;
	}

	/* enumerate all devices in this information set */
	usb_member_index = 0;
	while (1) {
		ZeroMemory(&usb_dev_instance, sizeof(usb_dev_instance));
		usb_dev_instance.cbSize = sizeof(usb_dev_instance);

		/* from information set, get device by index */
		ret = SetupDiEnumDeviceInfo(usb_dev_info_set, usb_member_index, &usb_dev_instance);
		if (ret == FALSE) {
			error_code = GetLastError();
			if (error_code == ERROR_NO_MORE_ITEMS) {
				break;
			}
			else {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				free_jstrarraylist(&list);
				throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(error_code), NULL);
				return NULL;
			}
		}

		/* for this device find its instance ID (USB\VID_04D8&PID_00DF\000098037)
		 * this is the variable 'Device Instance Path' in device manager. */
		memset(buffer, '\0', sizeof(buffer));
		ret = SetupDiGetDeviceInstanceId(usb_dev_info_set, &usb_dev_instance, buffer, sizeof(buffer), &size);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}

		/* USB-IF vendor ID, extract and match, if matched continue further otherwise loop back */
		x = 0;
		while (buffer[x] != '\0') {
			if((buffer[x] == 'V') && (buffer[x + 1] == 'I') && (buffer[x + 2] == 'D') && (buffer[x + 3] == '_')) {
				break;
			}
			x++;
		}
		x = x + 4;
		i = 0;
		while (buffer[x] != '&') {
			charbuffer[i] = buffer[x];
			i++;
			x++;
		}
		charbuffer[i] = '\0'; /* indicate end of string */

		vid = (int)_tcstol(charbuffer, &ptrend, 16);

		if(vendor_to_match != 0) {
			/* we need to apply filter for identify specific vendor */
			if(vid != vendor_to_match) {
				usb_member_index++;
				continue;
			}
		}

		usb_dev_info = (*env)->NewString(env, charbuffer, (jsize) _tcslen(charbuffer));
		if((usb_dev_info == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRSTR);
			return NULL;
		}
		insert_jstrarraylist(&list, usb_dev_info);

		/* USB product ID */
		x = 6;
		while (buffer[x] != '\0') {
			if ((buffer[x] == 'P') && (buffer[x + 1] == 'I') && (buffer[x + 2] == 'D') && (buffer[x + 3] == '_')) {
				break;
			}
			x++;
		}
		x = x + 4;
		i = 0;
		while (buffer[x] != '\\') {
			charbuffer[i] = buffer[x];
			i++;
			x++;
		}
		charbuffer[i] = '\0';

		usb_dev_info = (*env)->NewString(env, charbuffer, (jsize) _tcslen(charbuffer));
		if ((usb_dev_info == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRSTR);
			return NULL;
		}
		insert_jstrarraylist(&list, usb_dev_info);

		/* SERIAL NUMBER */
		x++;
		i = 0;
		while (buffer[x] != '\0') {
			charbuffer[i] = buffer[x];
			i++;
			x++;
		}
		charbuffer[i] = '\0';

		usb_dev_info = (*env)->NewString(env, charbuffer, (jsize) _tcslen(charbuffer));
		if ((usb_dev_info == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRSTR);
			return NULL;
		}
		insert_jstrarraylist(&list, usb_dev_info);

		/* PRODUCT (idProduct field of USB device descriptor) */
		memset(buffer, '\0', sizeof(buffer));
		ret = SetupDiGetDeviceProperty(usb_dev_info_set, &usb_dev_instance, &DEVPKEY_Device_BusReportedDeviceDesc, &proptype, (BYTE *)buffer, sizeof(buffer), &size, 0);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}
		usb_dev_info = (*env)->NewString(env, buffer, (jsize) _tcslen(buffer));
		insert_jstrarraylist(&list, usb_dev_info);

		/* MANUFACTURER */
		memset(buffer, '\0', sizeof(buffer));
		ret = SetupDiGetDeviceProperty(usb_dev_info_set, &usb_dev_instance, &DEVPKEY_Device_Manufacturer, &proptype, (BYTE *)buffer, sizeof(buffer), &size, 0);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}
		usb_dev_info = (*env)->NewString(env, buffer, (jsize) _tcslen(buffer));
		insert_jstrarraylist(&list, usb_dev_info);

		/* LOCATION (Location paths + Location info, get separately and then create a single string) */
		memset(buffer, '\0', sizeof(buffer));
		ret = SetupDiGetDeviceRegistryProperty(usb_dev_info_set, &usb_dev_instance, SPDRP_LOCATION_PATHS, &regproptype, (BYTE *)buffer, sizeof(buffer), &size);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}

		memset(charbuffer, '\0', sizeof(charbuffer));
		ret = SetupDiGetDeviceRegistryProperty(usb_dev_info_set, &usb_dev_instance, SPDRP_LOCATION_INFORMATION, &regproptype, (BYTE *)charbuffer, sizeof(charbuffer), &size);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}

		i = 0;
		x = (int)_tcslen(buffer);
		buffer[x] = '-';
		x++;
		for (i = 0; i < (int)_tcslen(charbuffer); i++) {
			buffer[x] = charbuffer[i];
			x++;
		}
		buffer[x] = '\0';

		usb_dev_info = (*env)->NewString(env, buffer, (jsize)_tcslen(buffer));
		insert_jstrarraylist(&list, usb_dev_info);

		/* loop to get next USB device */
		usb_member_index++;
	}

	SetupDiDestroyDeviceInfoList(usb_dev_info_set);

	strClass = (*env)->FindClass(env, JAVALSTRING);
	if((strClass == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		(*env)->ExceptionClear(env);
		free_jstrarraylist(&list);
		throw_serialcom_exception(env, 3, 0, E_FINDCLASSSSTRINGSTR);
		return NULL;
	}

	usbDevicesFound = (*env)->NewObjectArray(env, (jsize)list.index, strClass, NULL);
	if((usbDevicesFound == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		(*env)->ExceptionClear(env);
		free_jstrarraylist(&list);
		throw_serialcom_exception(env, 3, 0, E_NEWOBJECTARRAYSTR);
		return NULL;
	}

	for (x = 0; x < list.index; x++) {
		(*env)->SetObjectArrayElement(env, usbDevicesFound, x, list.base[x]);
		if ((*env)->ExceptionOccurred(env)) {
			(*env)->ExceptionClear(env);
			free_jstrarraylist(&list);
			throw_serialcom_exception(env, 3, 0, E_SETOBJECTARRAYSTR);
			return NULL;
		}
	}

	free_jstrarraylist(&list);
	return usbDevicesFound;
}
/*
 * Find the name of the most specific driver which is currently associated with the 
 * given HID device instance.
 *
 * A HID device can be on USB, I2C, BLUETOOTH or pseudo.
 */
jstring find_driver_for_given_hiddevice(JNIEnv *env, jstring hidDevNode) {

	int x = 0;
	BOOL ret = FALSE;
	LONG status = 0;
	DWORD error_code = 0;
	DWORD errorVal = 0;
	DWORD size = 0;
	DWORD charbuffer_size = 0;
	DWORD driver_name_size = 0;
	ULONG buffer_size = 0;
	DWORD hid_member_index = 0;
	HDEVINFO hid_dev_info_set;
	SP_DEVINFO_DATA hid_dev_instance;
	ULONG devprop_buffer_size = 0;
	const jchar* device_node = NULL;
	jstring driver_name = NULL;

	/* size of these buffers is hardcoded in functions using them */
	TCHAR buffer[1024];
	TCHAR keybuf[1024];
	TCHAR charbuffer[128];

	/* extract HID device name to match (as an array of Unicode characters) */
	device_node = (*env)->GetStringChars(env, hidDevNode, JNI_FALSE);
	if ((device_node == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRCHARSTR);
		return NULL;
	}

	/* get information set for all usb devices matching the GUID */
	hid_dev_info_set = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
	if (hid_dev_info_set == INVALID_HANDLE_VALUE) {
		SetupDiDestroyDeviceInfoList(hid_dev_info_set);
		(*env)->ReleaseStringChars(env, hidDevNode, device_node);
		throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
		return NULL;
	}

	/* enumerate all devices in this information set */
	hid_member_index = 0;
	while (1) {
		ZeroMemory(&hid_dev_instance, sizeof(hid_dev_instance));
		hid_dev_instance.cbSize = sizeof(hid_dev_instance);

		/* from information set, get device by index */
		ret = SetupDiEnumDeviceInfo(hid_dev_info_set, hid_member_index, &hid_dev_instance);
		if (ret == FALSE) {
			error_code = GetLastError();
			if (error_code == ERROR_NO_MORE_ITEMS) {
				break;
			}else {
				SetupDiDestroyDeviceInfoList(hid_dev_info_set);
				(*env)->ReleaseStringChars(env, hidDevNode, device_node);
				throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(error_code), NULL);
				return NULL;
			}
		}

		/* for this device find its instance ID, for example; HID\VID_04D8&PID_00DF&MI_02\7&33842C3F&0&0000
		 * this is variable 'Device Instance Path' in device manager. */
		memset(buffer, '\0', 1024);
		ret = SetupDiGetDeviceInstanceId(hid_dev_info_set, &hid_dev_instance, buffer, 1024, &size);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(hid_dev_info_set);
			(*env)->ReleaseStringChars(env, hidDevNode, device_node);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return NULL;
		}

		/* match device path */
		ret = _tcsicmp(device_node, buffer);
		if (ret != 0) {
			hid_member_index++;
			continue;
		}

		/* reaching here means this is the device for which driver is to be found. 
		   HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\HID\VID_VID+PID_PID\Serial_Number */
		memset(keybuf, '\0', 1024);
		_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s"), buffer);

		/* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\HID\VID_VID+PID_PID\Serial_Number\Service */
		charbuffer_size = sizeof(charbuffer);
		memset(charbuffer, '\0', 128);
		status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("Service"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
		if (status != ERROR_SUCCESS) {
			errorVal = GetLastError();
			if (errorVal == 0x00) {
				/* this indicates Service registery entry does not exist, we assume windows provided 
				   default HidClass driver is driving this device */
				driver_name = (*env)->NewStringUTF(env, "HidClass");
				if ((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
					(*env)->ExceptionClear(env);
					SetupDiDestroyDeviceInfoList(hid_dev_info_set);
					(*env)->ReleaseStringChars(env, hidDevNode, device_node);
					throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
					return NULL;
				}
				return driver_name;
			}else {
				SetupDiDestroyDeviceInfoList(hid_dev_info_set);
				(*env)->ReleaseStringChars(env, hidDevNode, device_node);
				throw_serialcom_exception(env, 4, errorVal, NULL);
				return NULL;
			}
		}

		/* return the driver name found as indicated by Service registry entry */
		driver_name = (*env)->NewString(env, charbuffer, (jsize)_tcslen(charbuffer));
		if ((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			(*env)->ExceptionClear(env);
			SetupDiDestroyDeviceInfoList(hid_dev_info_set);
			(*env)->ReleaseStringChars(env, hidDevNode, device_node);
			throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
			return NULL;
		}

		SetupDiDestroyDeviceInfoList(hid_dev_info_set);
		(*env)->ReleaseStringChars(env, hidDevNode, device_node);
		return driver_name;
	}

	/* reaching here means that the no driver was found for given device path, 
	   return empty string */
	driver_name = (*env)->NewStringUTF(env, "");
	if ((driver_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		(*env)->ExceptionClear(env);
		throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
		return NULL;
	}
	return driver_name;
}
/*
 * Get the current latency timer value for ftdi devices.
 * return value read on success or -1 if any error occurs.
 */
jint get_latency_timer_value(JNIEnv *env, jstring comPortName) {

	const jchar* port_name = NULL;
	int x = 0;
	BOOL ret = FALSE;
	LONG status = 0;
	DWORD error_code = 0;
	DWORD size = 0;
	DWORD regproptype;
	DWORD charbuffer_size = 0;
	DWORD driver_name_size = 0;
	ULONG buffer_size = 0;
	DWORD usb_member_index = 0;
	HDEVINFO usb_dev_info_set;
	SP_DEVINFO_DATA usb_dev_instance;
	ULONG devprop_buffer_size = 0;
	DEVPROPTYPE proptype;
	CONFIGRET cmret = 0;
	DEVINST firstchild = 0;
	DEVINST next_sibling = 0;
	DEVINST current_sibling = 0;
	DWORD timer_value;
	DWORD timervalue_size;

	/* size of these buffers is hardcoded in functions using them */
	TCHAR buffer[1024];
	TCHAR devprop_buffer[1024];
	TCHAR keybuf[1024];
	TCHAR charbuffer[128];
	char cmerror[256];

	/* extract com port name to match (as an array of Unicode characters) */
	port_name = (*env)->GetStringChars(env, comPortName, JNI_FALSE);
	if ((port_name == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return -1;
	}

	/* get information set for all usb devices matching the GUID */
	usb_dev_info_set = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
	if (usb_dev_info_set == INVALID_HANDLE_VALUE) {
		SetupDiDestroyDeviceInfoList(usb_dev_info_set);
		(*env)->ReleaseStringChars(env, comPortName, port_name);
		throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
		return -1;
	}

	/* enumerate all devices in this information set */
	usb_member_index = 0;

	while (1) {
		ZeroMemory(&usb_dev_instance, sizeof(usb_dev_instance));
		usb_dev_instance.cbSize = sizeof(usb_dev_instance);

		/* from information set, get device by index */
		ret = SetupDiEnumDeviceInfo(usb_dev_info_set, usb_member_index, &usb_dev_instance);
		if (ret == FALSE) {
			error_code = GetLastError();
			if (error_code == ERROR_NO_MORE_ITEMS) {
				break;
			}
			else {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(error_code), NULL);
				return -1;
			}
		}

		/* for this device find its instance ID (USB\VID_04D8&PID_00DF\000098037)
		 * this is variable 'Device Instance Path' in device manager. */
		memset(buffer, '\0', 1024);
		ret = SetupDiGetDeviceInstanceId(usb_dev_info_set, &usb_dev_instance, buffer, 1024, &size);
		if (ret == FALSE) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			(*env)->ReleaseStringChars(env, comPortName, port_name);
			throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
			return -1;
		}

		cmret = CM_Get_Child(&firstchild, usb_dev_instance.DevInst, 0);
		if (cmret != CR_SUCCESS) {
			if (cmret == CR_NO_SUCH_DEVNODE) {
				/* this device does not have any child, so check if this device itself is a "Ports" class device or not */
				memset(devprop_buffer, '\0', 1024);
				ret = SetupDiGetDeviceRegistryProperty(usb_dev_info_set, &usb_dev_instance, SPDRP_CLASSGUID, &regproptype, (BYTE *)devprop_buffer, sizeof(devprop_buffer), &size);
				if (ret == FALSE) {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
					return -1;
				}

				/* match GUID */
				ret = _tcsicmp(devprop_buffer, TEXT("{4D36E978-E325-11CE-BFC1-08002BE10318}"));
				if (ret != 0) {
					usb_member_index++;
					continue;
				}

				/* reaching here means that the device is COM port device (CDC/ACM), get its COM port name/number
				 * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_VID+PID_PID+Serial_Number\0000\DeviceParameters\PortName
				 * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_VID+PID_PID\Serial_Number\DeviceParameters\PortName */
				memset(keybuf, '\0', 1024);
				_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s\\Device Parameters"), buffer);

				charbuffer_size = sizeof(charbuffer);
				memset(charbuffer, '\0', 128);
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("PortName"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 4, GetLastError(), NULL);
					return -1;
				}

				/* match port name */
				ret = _tcsicmp(charbuffer, port_name);
				if (ret != 0) {
					usb_member_index++;
					continue;
				}

				/* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_VID+PID_PID+Serial_Number\0000\DeviceParameters\LatencyTimer */
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("LatencyTimer"), RRF_RT_REG_DWORD, NULL, &timer_value, &timervalue_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 4, status, NULL);
					return -1;
				}

				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				return (jint)timer_value;
			}else {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				_snprintf_s(cmerror, 256, 256, "CM_Get_DevNode_Registry_Property CR_xxxx error code : 0x%X\0", cmret);
				throw_serialcom_exception(env, 3, 0, cmerror);
				return -1;
			}
		}

		/* reaching here means that this USB device has at-least one child device node, examine child now */

		devprop_buffer_size = sizeof(devprop_buffer);
		memset(devprop_buffer, '\0', sizeof(devprop_buffer));
		cmret = CM_Get_DevNode_Registry_Property(firstchild, CM_DRP_CLASSGUID, &proptype, (PVOID)devprop_buffer, &devprop_buffer_size, 0);
		if (cmret != CR_SUCCESS) {
			SetupDiDestroyDeviceInfoList(usb_dev_info_set);
			(*env)->ReleaseStringChars(env, comPortName, port_name);
			_snprintf_s(cmerror, 256, 256, "CM_Get_DevNode_Registry_Property CR_xxxx error code : 0x%X\0", cmret);
			throw_serialcom_exception(env, 3, 0, cmerror);
			return -1;
		}

		/* match GUID */
		ret = _tcsicmp(devprop_buffer, TEXT("{4D36E978-E325-11CE-BFC1-08002BE10318}"));
		if (ret == 0) {
			/* reaching here means that the child device is COM port device (CDC/ACM), get its COM port name/number */
			memset(buffer, '\0', 1024);
			cmret = CM_Get_Device_ID(firstchild, buffer, 1024, 0);
			if (cmret != CR_SUCCESS) {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				_snprintf_s(cmerror, 256, 256, "CM_Get_Device_ID CR_xxxx error code : 0x%X\0", cmret);
				throw_serialcom_exception(env, 3, 0, cmerror);
				return -1;
			}

			memset(keybuf, '\0', 1024);
			_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s\\Device Parameters"), buffer);

			charbuffer_size = sizeof(charbuffer);
			memset(charbuffer, '\0', 128);

			status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("PortName"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
			if (status != ERROR_SUCCESS) {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				throw_serialcom_exception(env, 4, GetLastError(), NULL);
				return -1;
			}

			/* match port name */
			ret = _tcsicmp(charbuffer, port_name);
			if (ret == 0) {
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("LatencyTimer"), RRF_RT_REG_DWORD, NULL, &timer_value, &timervalue_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 4, GetLastError(), NULL);
					return -1;
				}

				/* clean up and return timer value found */
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				return (jint)timer_value;
			}
		}

		/* reaching here means first child of this USB device was not CDC/ACM interface, need to
		   iterate over all the interfaces (siblings) this device has examining them for matching our criteria */
		current_sibling = firstchild;
		while (1) {
			cmret = CM_Get_Sibling(&next_sibling, current_sibling, 0);
			if (cmret != CR_SUCCESS) {
				if (cmret == CR_NO_SUCH_DEVNODE) {
					/* done iterating over all interfaces, move to next examine next USB device */
					break;
				}
				else {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					_snprintf_s(cmerror, 256, 256, "CM_Get_Sibling failed with CR_xxxx error code : 0x%X\0", cmret);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 3, 0, cmerror);
					return -1;
				}

			}

			/* reaching here means USB device has more than 1 interfaces, get class of this interface (sibling) */
			devprop_buffer_size = sizeof(devprop_buffer);
			memset(devprop_buffer, '\0', sizeof(devprop_buffer));
			cmret = CM_Get_DevNode_Registry_Property(next_sibling, CM_DRP_CLASSGUID, &proptype, (VOID *)devprop_buffer, &devprop_buffer_size, 0);
			if (cmret != CR_SUCCESS) {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				_snprintf_s(cmerror, 256, 256, "CM_Get_DevNode_Registry_Property failed with CR_xxxx error code : 0x%X\0", cmret);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				throw_serialcom_exception(env, 3, 0, cmerror);
				return -1;
			}

			/* match GUID for this sibling */
			ret = _tcsicmp(devprop_buffer, TEXT("{4D36E978-E325-11CE-BFC1-08002BE10318}"));
			if (ret != 0) {
				/* this interface is not CDC/ACM, loop over to next interface */
				current_sibling = next_sibling;
				continue;
			}

			/* reaching here means that this sibling (interface) is a CDC/ACM type, get its COM port name/number */
			buffer_size = (ULONG)_tcslen(buffer);
			memset(buffer, '\0', 1024);
			cmret = CM_Get_Device_ID(next_sibling, buffer, 1024, 0);
			if (cmret != CR_SUCCESS) {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				_snprintf_s(cmerror, 256, 256, "CM_Get_Device_ID failed with CR_xxxx error code : 0x%X\0", cmret);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				throw_serialcom_exception(env, 3, 0, cmerror);
				return -1;
			}

			memset(keybuf, '\0', 1024);
			_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s\\Device Parameters"), buffer);

			charbuffer_size = sizeof(charbuffer);
			memset(charbuffer, '\0', 128);
			status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("PortName"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
			if (status != ERROR_SUCCESS) {
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				throw_serialcom_exception(env, 4, GetLastError(), NULL);
				return -1;
			}

			/* match port name */
			ret = _tcsicmp(charbuffer, port_name);
			if (ret == 0) {
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("LatencyTimer"), RRF_RT_REG_DWORD, NULL, &timer_value, &timervalue_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(usb_dev_info_set);
					(*env)->ReleaseStringChars(env, comPortName, port_name);
					throw_serialcom_exception(env, 4, GetLastError(), NULL);
					return -1;
				}

				/* clean up and return timer value found */
				SetupDiDestroyDeviceInfoList(usb_dev_info_set);
				(*env)->ReleaseStringChars(env, comPortName, port_name);
				return (jint)timer_value;
			}

			/* set this sibling as base sibling for fetching next sibling, loop over to get and check next
			interface (sibling) */
			current_sibling = next_sibling;
		}

		/* increment to get and examine the next usb device for COM ports class */
		usb_member_index++;
	}

	/* reaching here means that probably given com port does not represent ftdi device or does not exist at all */
	SetupDiDestroyDeviceInfoList(usb_dev_info_set);
	(*env)->ReleaseStringChars(env, comPortName, port_name);
	throw_serialcom_exception(env, 3, 0, E_NOTFTDIPORT);
	return -1;
}
/*
 * get the current latency timer value for ftdi devices.
 * return value read on success or -1 if any error occurs.
 */
jint get_latency_timer_value(JNIEnv *env, jstring comPortName) {

	struct udev *udev_ctx;
	struct udev_enumerate *enumerator;
	struct udev_list_entry *devices, *dev_list_entry;
	const char *device_path;
	const char *device_node;
	const char *path;
	struct udev_device *udev_device;
	const char *com_port_to_match = NULL;
	char buffer[512];
	int fd = 0;
	int ret = 0;
	char *endptr;
	jint timer_value = 0;

	udev_ctx = udev_new();
	enumerator = udev_enumerate_new(udev_ctx);
	udev_enumerate_add_match_subsystem(enumerator, "tty");
	udev_enumerate_scan_devices(enumerator);
	devices = udev_enumerate_get_list_entry(enumerator);

	com_port_to_match = (*env)->GetStringUTFChars(env, comPortName, NULL);
	if((com_port_to_match == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
		throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
		return -1;
	}

	udev_list_entry_foreach(dev_list_entry, devices) {
		path = udev_list_entry_get_name(dev_list_entry);
		udev_device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), path);
		if(udev_device == NULL) {
			continue;
		}

		/* match the device node, if matched get device path, and create absolute path to
		 * latency file and read its value */
		device_node = udev_device_get_devnode(udev_device);
		if(device_node != NULL) {
			if(strcmp(device_node, com_port_to_match) == 0) {

				device_path = udev_device_get_devpath(udev_device);
				if(device_path != NULL) {
					memset(buffer, '\0', sizeof(buffer));
					snprintf(buffer, 512, "/sys%s/device/latency_timer", device_path);

					errno = 0;
					fd = open(buffer, O_RDONLY);
					if(fd < 0) {
						(*env)->ReleaseStringUTFChars(env, comPortName, com_port_to_match);
						throw_serialcom_exception(env, 1, errno, NULL);
						return -1;
					}

					memset(buffer, '\0', sizeof(buffer));
					errno = 0;
					ret = read(fd, buffer, 512);
					if(ret < 0) {
						(*env)->ReleaseStringUTFChars(env, comPortName, com_port_to_match);
						throw_serialcom_exception(env, 1, errno, NULL);
						return -1;
					}
					close(fd);

					timer_value = (jint) strtol(buffer, &endptr, 10);
					(*env)->ReleaseStringUTFChars(env, comPortName, com_port_to_match);
					udev_device_unref(udev_device);
					udev_enumerate_unref(enumerator);
					udev_unref(udev_ctx);
					return timer_value;
				}
			}
		}

		udev_device_unref(udev_device);
	}
jobjectArray getusb_firmware_version(JNIEnv *env, jint usbvid_to_match, jint usbpid_to_match, jstring serial_number) {

	int x = 0;
	int vid = 0;
	int pid = 0;
	struct udev *udev_ctx;
	struct udev_enumerate *enumerator;
	struct udev_list_entry *devices, *dev_list_entry;
	const char *sysattr_val;
	const char *path;
	struct udev_device *udev_device;
	char *endptr;
	char buffer[128];
	const char* serial = NULL;
	const char *prop_val;
	struct jstrarray_list list = {0};
	jstring usb_dev_info;
	jclass strClass = NULL;
	jobjectArray usbDevicesFwVerFound = NULL;

	if(serial_number != NULL) {
		serial = (*env)->GetStringUTFChars(env, serial_number, NULL);
		if((serial == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
			return NULL;
		}
	}

	init_jstrarraylist(&list, 10);

	/* libudev is reference counted. Memory is freed when counts reach to zero. */
	udev_ctx = udev_new();
	enumerator = udev_enumerate_new(udev_ctx);
	udev_enumerate_add_match_subsystem(enumerator, "usb");
	udev_enumerate_scan_devices(enumerator);
	devices = udev_enumerate_get_list_entry(enumerator);

	udev_list_entry_foreach(dev_list_entry, devices) {
		path = udev_list_entry_get_name(dev_list_entry);
		udev_device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), path);
		if(udev_device == NULL) {
			continue;
		}

		if(strcmp("usb_device", udev_device_get_devtype(udev_device)) == 0) {

			/* match vid */
			sysattr_val = udev_device_get_sysattr_value(udev_device, "idVendor");
			if(sysattr_val != NULL) {
				vid = 0x0000FFFF & (int)strtol(sysattr_val, &endptr, 16);
				if(vid != usbvid_to_match) {
					udev_device_unref(udev_device);
					continue;
				}
			}else {
				udev_device_unref(udev_device);
				continue;
			}

			/* match pid */
			sysattr_val = udev_device_get_sysattr_value(udev_device, "idProduct");
			if(sysattr_val != NULL) {
				pid = 0x0000FFFF & (int)strtol(sysattr_val, &endptr, 16);
				if(pid != usbpid_to_match) {
					udev_device_unref(udev_device);
					continue;
				}
			}else {
				udev_device_unref(udev_device);
				continue;
			}

			/* match serial number if requested by application */
			if (serial != NULL) {
				sysattr_val = udev_device_get_sysattr_value(udev_device, "serial");
				if(sysattr_val != NULL) {
					if(strcasecmp(sysattr_val, serial) != 0) {
						udev_device_unref(udev_device);
						continue;
					}
				}else {
					udev_device_unref(udev_device);
					continue;
				}
			}

			/* reaching here means that this is the device whose firmware version application need to know. */
			prop_val = udev_device_get_property_value(udev_device, "ID_REVISION");
			memset(buffer, '\0', sizeof(buffer));
			snprintf(buffer, 128, "%d", (0x0000FFFF & (int)strtol(prop_val, &endptr, 16)));
			usb_dev_info = (*env)->NewStringUTF(env, buffer);
			if((usb_dev_info == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
				free_jstrarraylist(&list);
				udev_device_unref(udev_device);
				udev_enumerate_unref(enumerator);
				udev_unref(udev_ctx);
				throw_serialcom_exception(env, 3, 0, E_NEWSTRUTFSTR);
				return NULL;
			}
			insert_jstrarraylist(&list, usb_dev_info);
		}
		udev_device_unref(udev_device);
	}
/*
 * Finds if a USB device whose VID, PID and serial number is given is connected to system
 * or not using platform specific APIs.
 *
 * Returns 1 if device is connected, returns 0 if not connected, -1 if an error occurs.
 */
jint is_usb_dev_connected(JNIEnv *env, jint usbvid_to_match, jint usbpid_to_match, jstring serial_number) {

	int vid = 0;
	int pid = 0;
	struct udev *udev_ctx;
	struct udev_enumerate *enumerator;
	struct udev_list_entry *devices, *dev_list_entry;
	const char *sysattr_val;
	const char *path;
	struct udev_device *udev_device;
	char *endptr;
	const char* serial = NULL;

	if(serial_number != NULL) {
		serial = (*env)->GetStringUTFChars(env, serial_number, NULL);
		if((serial == NULL) || ((*env)->ExceptionOccurred(env) != NULL)) {
			throw_serialcom_exception(env, 3, 0, E_GETSTRUTFCHARSTR);
			return -1;
		}
	}

	/* libudev is reference counted. Memory is freed when counts reach to zero. */
	udev_ctx = udev_new();
	enumerator = udev_enumerate_new(udev_ctx);
	udev_enumerate_add_match_subsystem(enumerator, "usb");
	udev_enumerate_scan_devices(enumerator);
	devices = udev_enumerate_get_list_entry(enumerator);

	udev_list_entry_foreach(dev_list_entry, devices) {
		path = udev_list_entry_get_name(dev_list_entry);
		udev_device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), path);
		if(udev_device == NULL) {
			continue;
		}

		if(strcmp("usb_device", udev_device_get_devtype(udev_device)) == 0) {

			/* match vid */
			sysattr_val = udev_device_get_sysattr_value(udev_device, "idVendor");
			if(sysattr_val != NULL) {
				vid = 0x0000FFFF & (int)strtol(sysattr_val, &endptr, 16);
				if(vid != usbvid_to_match) {
					udev_device_unref(udev_device);
					continue;
				}
			}else {
				udev_device_unref(udev_device);
				continue;
			}

			/* match pid */
			sysattr_val = udev_device_get_sysattr_value(udev_device, "idProduct");
			if(sysattr_val != NULL) {
				pid = 0x0000FFFF & (int)strtol(sysattr_val, &endptr, 16);
				if(pid != usbpid_to_match) {
					udev_device_unref(udev_device);
					continue;
				}
			}else {
				udev_device_unref(udev_device);
				continue;
			}

			/* match serial number if requested by application */
			if (serial != NULL) {
				sysattr_val = udev_device_get_sysattr_value(udev_device, "serial");
				if(sysattr_val != NULL) {
					if(strcasecmp(sysattr_val, serial) != 0) {
						udev_device_unref(udev_device);
						continue;
					}
				}else {
					udev_device_unref(udev_device);
					continue;
				}
			}

			/* reaching here means device is connected to system at present which matches given criteria. */
			udev_device_unref(udev_device);
			udev_enumerate_unref(enumerator);
			udev_unref(udev_ctx);
			return 1;
		}
		udev_device_unref(udev_device);
	}
/*
 * Check if the given COM port name belongs to a multi port serial adaptor device. If yes, get its driver name
 * and return to caller.
 *
 * Return 1 if found, 0 if not found, -1 if an error occurs (also throws exception in this case).
 */
int get_driver_com_port_multiportadaptor(JNIEnv *env, const jchar *port_name, TCHAR *driver_name) {

	int x = 0;
	BOOL ret = FALSE;
	LONG status = 0;
	DWORD error_code = 0;
	DWORD size = 0;
	DWORD regproptype;
	DWORD charbuffer_size = 0;
	DWORD driver_name_size = 0;
	ULONG buffer_size = 0;
	DWORD multiport_member_index = 0;
	HDEVINFO multiport_dev_info_set;
	SP_DEVINFO_DATA multiport_dev_instance;
	ULONG devprop_buffer_size = 0;
	DEVPROPTYPE proptype;
	CONFIGRET cmret = 0;
	DEVINST firstchild = 0;
	DEVINST next_sibling = 0;
	DEVINST current_sibling = 0;

	/* size of these buffers is hardcoded in functions using them */
	TCHAR buffer[1024];
	TCHAR devprop_buffer[1024];
	TCHAR keybuf[1024];
	TCHAR charbuffer[128];
	char cmerror[256];

	/* get information set for all multi port serial adaptor devices matching the GUID */
	multiport_dev_info_set = SetupDiGetClassDevs(&GUID_MULTIPORT_SERIAL_ADAPTOR_DEVICE, NULL, NULL, DIGCF_DEVICEINTERFACE);
	if (multiport_dev_info_set == INVALID_HANDLE_VALUE) {
		SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
		throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(GetLastError()), NULL);
		return -1;
	}

	/* enumerate all devices in this information set */
	multiport_member_index = 0;
	while (1) {
		ZeroMemory(&multiport_dev_instance, sizeof(multiport_dev_instance));
		multiport_dev_instance.cbSize = sizeof(multiport_dev_instance);

		/* from information set, get device by index */
		ret = SetupDiEnumDeviceInfo(multiport_dev_info_set, multiport_member_index, &multiport_dev_instance);
		if (ret == FALSE) {
			error_code = GetLastError();
			if (error_code == ERROR_NO_MORE_ITEMS) {
				break;
			}else {
				SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
				throw_serialcom_exception(env, 4, HRESULT_FROM_SETUPAPI(error_code), NULL);
				return -1;
			}
		}

		/* for this device find its instance ID (USB\VID_04D8&PID_00DF\000098037)
		 * this is variable 'Device Instance Path' in device manager. */
		cmret = CM_Get_Device_ID(multiport_dev_instance.DevInst, buffer, 1024, 0);
		if (cmret != CR_SUCCESS) {
			SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
			_snprintf_s(cmerror, 256, 256, "CM_Get_Device_ID CR_xxxx error code : 0x%X\0", cmret);
			throw_serialcom_exception(env, 3, 0, cmerror);
			return -1;
		}

		/* get its COM port name/number for this device */
		memset(keybuf, '\0', 1024);
		_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s\\Device Parameters"), buffer);

		charbuffer_size = sizeof(charbuffer);
		memset(charbuffer, '\0', 128);
		/* ignore error as some devices might not have portname registry key */
		status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("PortName"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
		if (status == ERROR_SUCCESS) {
			/* match port name */
			ret = _tcsicmp(charbuffer, port_name);
			if (ret == 0) {
				/* get driver name */
				memset(keybuf, '\0', 1024);
				_stprintf_s(keybuf, 1024, TEXT("SYSTEM\\CurrentControlSet\\Enum\\%s"), buffer);

				/* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\XXXX\XXX\XXX\Service */
				charbuffer_size = sizeof(charbuffer);
				memset(charbuffer, '\0', 128);
				status = RegGetValue(HKEY_LOCAL_MACHINE, keybuf, TEXT("Service"), RRF_RT_REG_SZ, NULL, (PVOID)charbuffer, &charbuffer_size);
				if (status != ERROR_SUCCESS) {
					SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
					throw_serialcom_exception(env, 4, GetLastError(), NULL);
					return -1;
				}

				/* populate array to be returned to caller */
				memset(driver_name, '\0', 128);
				for (x = 0; x < _tcslen(charbuffer); x++) {
					driver_name[x] = charbuffer[x];
				}

				/* clean up and return 1 to indicate driver found */
				SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
				return 1;
			}
		}

		/* increment to get and examine the next multiport device for COM ports class */
		multiport_member_index++;
	}

	/* reaching here means given COM port does not belong to multi-port serial adaptor device */
	SetupDiDestroyDeviceInfoList(multiport_dev_info_set);
	return 0;
}