/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
 */

#include "MtpDevice.h"
#include "MtpDebug.h"
#include "MtpDeviceInfo.h"
#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpStorageInfo.h"
#include "MtpStringBuffer.h"
#include "MtpUtils.h"
#include "MtpDataPacket.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <endian.h>

#include <usbhost/usbhost.h>


#if 0
static bool isMtpDevice(uint16_t vendor, uint16_t product) {
	// Sandisk Sansa Fuze
	if (vendor == 0x0781 && product == 0x74c2)
		return true;
	// Samsung YP-Z5
	if (vendor == 0x04e8 && product == 0x503c)
		return true;
	return false;
}
#endif

#ifdef HAS_USBHOST_TIMEOUT
static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
#endif

MtpDevice* MtpDevice::open(const char* deviceName, int fd) {
	struct usb_device *device = usb_device_new(deviceName, fd);
	if (!device) {
		MTPE("usb_device_new failed for %s", deviceName);
		return NULL;
	}

	struct usb_descriptor_header* desc;
	struct usb_descriptor_iter iter;

	usb_descriptor_iter_init(device, &iter);

	while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
		if (desc->bDescriptorType == USB_DT_INTERFACE) {
			struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;

			if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
				interface->bInterfaceSubClass == 1 && // Still Image Capture
				interface->bInterfaceProtocol == 1)	 // Picture Transfer Protocol (PIMA 15470)
			{
#ifdef HAS_USBHOST_TIMEOUT
				char* manufacturerName = usb_device_get_manufacturer_name(device, USB_CONTROL_TRANSFER_TIMEOUT_MS);
				char* productName = usb_device_get_product_name(device, USB_CONTROL_TRANSFER_TIMEOUT_MS);
#else
				char* manufacturerName = usb_device_get_manufacturer_name(device);
				char* productName = usb_device_get_product_name(device);
#endif
				MTPD("Found camera: \"%s\" \"%s\"\n", manufacturerName, productName);
				free(manufacturerName);
				free(productName);
			} else if (interface->bInterfaceClass == 0xFF &&
					interface->bInterfaceSubClass == 0xFF &&
					interface->bInterfaceProtocol == 0) {
#ifdef HAS_USBHOST_TIMEOUT
				char* interfaceName = usb_device_get_string(device, interface->iInterface, USB_CONTROL_TRANSFER_TIMEOUT_MS);
#else
				char* interfaceName = usb_device_get_string(device, interface->iInterface);
#endif
				if (!interfaceName) {
					continue;
				} else if (strcmp(interfaceName, "MTP")) {
					free(interfaceName);
					continue;
				}
				free(interfaceName);

				// Looks like an android style MTP device
#ifdef HAS_USBHOST_TIMEOUT
				char* manufacturerName = usb_device_get_manufacturer_name(device, USB_CONTROL_TRANSFER_TIMEOUT_MS);
				char* productName = usb_device_get_product_name(device, USB_CONTROL_TRANSFER_TIMEOUT_MS);
#else
				char* manufacturerName = usb_device_get_manufacturer_name(device);
				char* productName = usb_device_get_product_name(device);
#endif
				MTPI("Found MTP device: \"%s\" \"%s\"\n", manufacturerName, productName);
				free(manufacturerName);
				free(productName);
			}
#if 0
			 else {
				// look for special cased devices based on vendor/product ID
				// we are doing this mainly for testing purposes
				uint16_t vendor = usb_device_get_vendor_id(device);
				uint16_t product = usb_device_get_product_id(device);
				if (!isMtpDevice(vendor, product)) {
					// not an MTP or PTP device
					continue;
				}
				// request MTP OS string and descriptor
				// some music players need to see this before entering MTP mode.
				char buffer[256];
				memset(buffer, 0, sizeof(buffer));
				int ret = usb_device_control_transfer(device,
						USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD,
						USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE,
						0, buffer, sizeof(buffer), 0);
				MTPE("usb_device_control_transfer returned %d errno: %d\n", ret, errno);
				if (ret > 0) {
					MTPI("got MTP string %s\n", buffer);
					ret = usb_device_control_transfer(device,
							USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1,
							0, 4, buffer, sizeof(buffer), 0);
					MTPI("OS descriptor got %d\n", ret);
				} else {
					MTPI("no MTP string\n");
				}
			}
#endif
			// if we got here, then we have a likely MTP or PTP device

			// interface should be followed by three endpoints
			struct usb_endpoint_descriptor *ep;
			struct usb_endpoint_descriptor *ep_in_desc = NULL;
			struct usb_endpoint_descriptor *ep_out_desc = NULL;
			struct usb_endpoint_descriptor *ep_intr_desc = NULL;
			for (int i = 0; i < 3; i++) {
				ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
				if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
					MTPE("endpoints not found\n");
					usb_device_close(device);
					return NULL;
				}
				if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
					if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
						ep_in_desc = ep;
					else
						ep_out_desc = ep;
				} else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
					ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
					ep_intr_desc = ep;
				}
			}
			if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
				MTPE("endpoints not found\n");
				usb_device_close(device);
				return NULL;
			}

			if (usb_device_claim_interface(device, interface->bInterfaceNumber)) {
				MTPE("usb_device_claim_interface failed errno: %d\n", errno);
				usb_device_close(device);
				return NULL;
			}

			MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber,
						ep_in_desc, ep_out_desc, ep_intr_desc);
			mtpDevice->initialize();
			return mtpDevice;
		}
	}

	usb_device_close(device);
	MTPE("device not found");
	return NULL;
}

MtpDevice::MtpDevice(struct usb_device* device, int interface,
			const struct usb_endpoint_descriptor *ep_in,
			const struct usb_endpoint_descriptor *ep_out,
			const struct usb_endpoint_descriptor *ep_intr)
	:   mDevice(device),
		mInterface(interface),
		mRequestIn1(NULL),
		mRequestIn2(NULL),
		mRequestOut(NULL),
		mRequestIntr(NULL),
		mDeviceInfo(NULL),
		mSessionID(0),
		mTransactionID(0),
		mReceivedResponse(false)
{
	mRequestIn1 = usb_request_new(device, ep_in);
	mRequestIn2 = usb_request_new(device, ep_in);
	mRequestOut = usb_request_new(device, ep_out);
	mRequestIntr = usb_request_new(device, ep_intr);
}

MtpDevice::~MtpDevice() {
	close();
	for (size_t i = 0; i < mDeviceProperties.size(); i++)
		delete mDeviceProperties[i];
	usb_request_free(mRequestIn1);
	usb_request_free(mRequestIn2);
	usb_request_free(mRequestOut);
	usb_request_free(mRequestIntr);
}

void MtpDevice::initialize() {
	openSession();
	mDeviceInfo = getDeviceInfo();
	if (mDeviceInfo) {
		if (mDeviceInfo->mDeviceProperties) {
			int count = mDeviceInfo->mDeviceProperties->size();
			for (int i = 0; i < count; i++) {
				MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
				MtpProperty* property = getDevicePropDesc(propCode);
				if (property)
					mDeviceProperties.push(property);
			}
		}
	}
}

void MtpDevice::close() {
	if (mDevice) {
		usb_device_release_interface(mDevice, mInterface);
		usb_device_close(mDevice);
		mDevice = NULL;
	}
}

void MtpDevice::print() {
	if (mDeviceInfo) {
		mDeviceInfo->print();

		if (mDeviceInfo->mDeviceProperties) {
			MTPI("***** DEVICE PROPERTIES *****\n");
			int count = mDeviceInfo->mDeviceProperties->size();
			for (int i = 0; i < count; i++) {
				MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
				MtpProperty* property = getDevicePropDesc(propCode);
				if (property) {
					property->print();
					delete property;
				}
			}
		}
	}

	if (mDeviceInfo->mPlaybackFormats) {
			MTPI("***** OBJECT PROPERTIES *****\n");
		int count = mDeviceInfo->mPlaybackFormats->size();
		for (int i = 0; i < count; i++) {
			MtpObjectFormat format = (*mDeviceInfo->mPlaybackFormats)[i];
			MTPI("*** FORMAT: %s\n", MtpDebug::getFormatCodeName(format));
			MtpObjectPropertyList* props = getObjectPropsSupported(format);
			if (props) {
				for (size_t j = 0; j < props->size(); j++) {
					MtpObjectProperty prop = (*props)[j];
					MtpProperty* property = getObjectPropDesc(prop, format);
					if (property) {
						property->print();
						delete property;
					} else {
						MTPI("could not fetch property: %s",
								MtpDebug::getObjectPropCodeName(prop));
					}
				}
			}
		}
	}
}

const char* MtpDevice::getDeviceName() {
	if (mDevice)
		return usb_device_get_name(mDevice);
	else
		return "???";
}

bool MtpDevice::openSession() {
	android::Mutex::Autolock autoLock(mMutex);

	mSessionID = 0;
	mTransactionID = 0;
	MtpSessionID newSession = 1;
	mRequest.reset();
	mRequest.setParameter(1, newSession);
	if (!sendRequest(MTP_OPERATION_OPEN_SESSION))
		return false;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN)
		newSession = mResponse.getParameter(1);
	else if (ret != MTP_RESPONSE_OK)
		return false;

	mSessionID = newSession;
	mTransactionID = 1;
	return true;
}

bool MtpDevice::closeSession() {
	// FIXME
	return true;
}

MtpDeviceInfo* MtpDevice::getDeviceInfo() {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		MtpDeviceInfo* info = new MtpDeviceInfo;
		info->read(mData);
		return info;
	}
	return NULL;
}

MtpStorageIDList* MtpDevice::getStorageIDs() {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		return mData.getAUInt32();
	}
	return NULL;
}

MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, storageID);
	if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		MtpStorageInfo* info = new MtpStorageInfo(storageID);
		info->read(mData);
		return info;
	}
	return NULL;
}

MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID,
			MtpObjectFormat format, MtpObjectHandle parent) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, storageID);
	mRequest.setParameter(2, format);
	mRequest.setParameter(3, parent);
	if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		return mData.getAUInt32();
	}
	return NULL;
}

MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) {
	android::Mutex::Autolock autoLock(mMutex);

	// FIXME - we might want to add some caching here

	mRequest.reset();
	mRequest.setParameter(1, handle);
	if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		MtpObjectInfo* info = new MtpObjectInfo(handle);
		info->read(mData);
		return info;
	}
	return NULL;
}

void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, handle);
	if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) {
		MtpResponseCode ret = readResponse();
		if (ret == MTP_RESPONSE_OK) {
			return mData.getData(outLength);
		}
	}
	outLength = 0;
	return NULL;
}

MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	MtpObjectHandle parent = info->mParent;
	if (parent == 0)
		parent = MTP_PARENT_ROOT;

	mRequest.setParameter(1, info->mStorageID);
	mRequest.setParameter(2, info->mParent);

	mData.putUInt32(info->mStorageID);
	mData.putUInt16(info->mFormat);
	mData.putUInt16(info->mProtectionStatus);
	mData.putUInt32(info->mCompressedSize);
	mData.putUInt16(info->mThumbFormat);
	mData.putUInt32(info->mThumbCompressedSize);
	mData.putUInt32(info->mThumbPixWidth);
	mData.putUInt32(info->mThumbPixHeight);
	mData.putUInt32(info->mImagePixWidth);
	mData.putUInt32(info->mImagePixHeight);
	mData.putUInt32(info->mImagePixDepth);
	mData.putUInt32(info->mParent);
	mData.putUInt16(info->mAssociationType);
	mData.putUInt32(info->mAssociationDesc);
	mData.putUInt32(info->mSequenceNumber);
	mData.putString(info->mName);

	char created[100], modified[100];
	formatDateTime(info->mDateCreated, created, sizeof(created));
	formatDateTime(info->mDateModified, modified, sizeof(modified));

	mData.putString(created);
	mData.putString(modified);
	if (info->mKeywords)
		mData.putString(info->mKeywords);
	else
		mData.putEmptyString();

   if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) {
		MtpResponseCode ret = readResponse();
		if (ret == MTP_RESPONSE_OK) {
			info->mStorageID = mResponse.getParameter(1);
			info->mParent = mResponse.getParameter(2);
			info->mHandle = mResponse.getParameter(3);
			return info->mHandle;
		}
	}
	return (MtpObjectHandle)-1;
}

bool MtpDevice::sendObject(MtpObjectInfo* info, int srcFD) {
	android::Mutex::Autolock autoLock(mMutex);

	int remaining = info->mCompressedSize;
	mRequest.reset();
	mRequest.setParameter(1, info->mHandle);
	if (sendRequest(MTP_OPERATION_SEND_OBJECT)) {
		// send data header
		writeDataHeader(MTP_OPERATION_SEND_OBJECT, remaining);

		char buffer[65536];
		while (remaining > 0) {
			int count = read(srcFD, buffer, sizeof(buffer));
			if (count > 0) {
				int written = mData.write(mRequestOut, buffer, count);
				// FIXME check error
				remaining -= count;
			} else {
				break;
			}
		}
	}
	MtpResponseCode ret = readResponse();
	return (remaining == 0 && ret == MTP_RESPONSE_OK);
}

bool MtpDevice::deleteObject(MtpObjectHandle handle) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, handle);
	if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) {
		MtpResponseCode ret = readResponse();
		if (ret == MTP_RESPONSE_OK)
			return true;
	}
	return false;
}

MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) {
	MtpObjectInfo* info = getObjectInfo(handle);
	if (info) {
		MtpObjectHandle parent = info->mParent;
		delete info;
		return parent;
	} else {
		return -1;
	}
}

MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) {
	MtpObjectInfo* info = getObjectInfo(handle);
	if (info) {
		MtpObjectHandle storageId = info->mStorageID;
		delete info;
		return storageId;
	} else {
		return -1;
	}
}

MtpObjectPropertyList* MtpDevice::getObjectPropsSupported(MtpObjectFormat format) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, format);
	if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		return mData.getAUInt16();
	}
	return NULL;

}

MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, code);
	if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		MtpProperty* property = new MtpProperty;
		property->read(mData);
		return property;
	}
	return NULL;
}

MtpProperty* MtpDevice::getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format) {
	android::Mutex::Autolock autoLock(mMutex);

	mRequest.reset();
	mRequest.setParameter(1, code);
	mRequest.setParameter(2, format);
	if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROP_DESC))
		return NULL;
	if (!readData())
		return NULL;
	MtpResponseCode ret = readResponse();
	if (ret == MTP_RESPONSE_OK) {
		MtpProperty* property = new MtpProperty;
		property->read(mData);
		return property;
	}
	return NULL;
}

bool MtpDevice::readObject(MtpObjectHandle handle,
		bool (* callback)(void* data, int offset, int length, void* clientData),
		int objectSize, void* clientData) {
	android::Mutex::Autolock autoLock(mMutex);
	bool result = false;

	mRequest.reset();
	mRequest.setParameter(1, handle);
	if (sendRequest(MTP_OPERATION_GET_OBJECT)
			&& mData.readDataHeader(mRequestIn1)) {
		uint32_t length = mData.getContainerLength();
		if ((int)length - MTP_CONTAINER_HEADER_SIZE != objectSize) {
			MTPE("readObject error objectSize: %d, length: %d",
					objectSize, length);
			goto fail;
		}
		length -= MTP_CONTAINER_HEADER_SIZE;
		uint32_t remaining = length;
		int offset = 0;

		int initialDataLength = 0;
		void* initialData = mData.getData(initialDataLength);
		if (initialData) {
			if (initialDataLength > 0) {
				if (!callback(initialData, 0, initialDataLength, clientData))
					goto fail;
				remaining -= initialDataLength;
				offset += initialDataLength;
			}
			free(initialData);
		}

		// USB reads greater than 16K don't work
		char buffer1[16384], buffer2[16384];
		mRequestIn1->buffer = buffer1;
		mRequestIn2->buffer = buffer2;
		struct usb_request* req = mRequestIn1;
		void* writeBuffer = NULL;
		int writeLength = 0;

		while (remaining > 0 || writeBuffer) {
			if (remaining > 0) {
				// queue up a read request
				req->buffer_length = (remaining > sizeof(buffer1) ? sizeof(buffer1) : remaining);
				if (mData.readDataAsync(req)) {
					MTPE("readDataAsync failed");
					goto fail;
				}
			} else {
				req = NULL;
			}

			if (writeBuffer) {
				// write previous buffer
				if (!callback(writeBuffer, offset, writeLength, clientData)) {
					MTPE("write failed");
					// wait for pending read before failing
					if (req)
						mData.readDataWait(mDevice);
					goto fail;
				}
				offset += writeLength;
				writeBuffer = NULL;
			}

			// wait for read to complete
			if (req) {
				int read = mData.readDataWait(mDevice);
				if (read < 0)
					goto fail;

				if (read > 0) {
					writeBuffer = req->buffer;
					writeLength = read;
					remaining -= read;
					req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
				} else {
					writeBuffer = NULL;
				}
			}
		}

		MtpResponseCode response = readResponse();
		if (response == MTP_RESPONSE_OK)
			result = true;
	}

fail:
	return result;
}


// reads the object's data and writes it to the specified file path
bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int group, int perm) {
	MTPI("readObject: %s", destPath);
	int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC, 0640);
	if (fd < 0) {
		MTPE("open failed for %s", destPath);
		return false;
	}

	fchown(fd, getuid(), group);
	// set permissions
	int mask = umask(0);
	fchmod(fd, perm);
	umask(mask);

	android::Mutex::Autolock autoLock(mMutex);
	bool result = false;

	mRequest.reset();
	mRequest.setParameter(1, handle);
	if (sendRequest(MTP_OPERATION_GET_OBJECT)
			&& mData.readDataHeader(mRequestIn1)) {
		uint32_t length = mData.getContainerLength();
		if (length < MTP_CONTAINER_HEADER_SIZE)
			goto fail;
		length -= MTP_CONTAINER_HEADER_SIZE;
		uint32_t remaining = length;

		int initialDataLength = 0;
		void* initialData = mData.getData(initialDataLength);
		if (initialData) {
			if (initialDataLength > 0) {
				if (write(fd, initialData, initialDataLength) != initialDataLength) {
					free(initialData);
					goto fail;
				}
				remaining -= initialDataLength;
			}
			free(initialData);
		}

		// USB reads greater than 16K don't work
		char buffer1[16384], buffer2[16384];
		mRequestIn1->buffer = buffer1;
		mRequestIn2->buffer = buffer2;
		struct usb_request* req = mRequestIn1;
		void* writeBuffer = NULL;
		int writeLength = 0;

		while (remaining > 0 || writeBuffer) {
			if (remaining > 0) {
				// queue up a read request
				req->buffer_length = (remaining > sizeof(buffer1) ? sizeof(buffer1) : remaining);
				if (mData.readDataAsync(req)) {
					MTPE("readDataAsync failed");
					goto fail;
				}
			} else {
				req = NULL;
			}

			if (writeBuffer) {
				// write previous buffer
				if (write(fd, writeBuffer, writeLength) != writeLength) {
					MTPE("write failed");
					// wait for pending read before failing
					if (req)
						mData.readDataWait(mDevice);
					goto fail;
				}
				writeBuffer = NULL;
			}

			// wait for read to complete
			if (req) {
				int read = mData.readDataWait(mDevice);
				if (read < 0)
					goto fail;

				if (read > 0) {
					writeBuffer = req->buffer;
					writeLength = read;
					remaining -= read;
					req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
				} else {
					writeBuffer = NULL;
				}
			}
		}

		MtpResponseCode response = readResponse();
		if (response == MTP_RESPONSE_OK)
			result = true;
	}

fail:
	::close(fd);
	return result;
}

bool MtpDevice::sendRequest(MtpOperationCode operation) {
	MTPD("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation));
	mReceivedResponse = false;
	mRequest.setOperationCode(operation);
	if (mTransactionID > 0)
		mRequest.setTransactionID(mTransactionID++);
	int ret = mRequest.write(mRequestOut);
	mRequest.dump();
	return (ret > 0);
}

bool MtpDevice::sendData() {
	MTPD("sendData\n");
	mData.setOperationCode(mRequest.getOperationCode());
	mData.setTransactionID(mRequest.getTransactionID());
	int ret = mData.write(mRequestOut);
	mData.dump();
	return (ret > 0);
}

bool MtpDevice::readData() {
	mData.reset();
	int ret = mData.read(mRequestIn1);
	MTPD("readData returned %d\n", ret);
	if (ret >= MTP_CONTAINER_HEADER_SIZE) {
		if (mData.getContainerType() == MTP_CONTAINER_TYPE_RESPONSE) {
			MTPD("got response packet instead of data packet");
			// we got a response packet rather than data
			// copy it to mResponse
			mResponse.copyFrom(mData);
			mReceivedResponse = true;
			return false;
		}
		mData.dump();
		return true;
	}
	else {
		MTPE("readResponse failed\n");
		return false;
	}
}

bool MtpDevice::writeDataHeader(MtpOperationCode operation, int dataLength) {
	mData.setOperationCode(operation);
	mData.setTransactionID(mRequest.getTransactionID());
	return (!mData.writeDataHeader(mRequestOut, dataLength));
}

MtpResponseCode MtpDevice::readResponse() {
	MTPD("readResponse\n");
	if (mReceivedResponse) {
		mReceivedResponse = false;
		return mResponse.getResponseCode();
	}
	int ret = mResponse.read(mRequestIn1);
	// handle zero length packets, which might occur if the data transfer
	// ends on a packet boundary
	if (ret == 0)
		ret = mResponse.read(mRequestIn1);
	if (ret >= MTP_CONTAINER_HEADER_SIZE) {
		mResponse.dump();
		return mResponse.getResponseCode();
	} else {
		MTPE("readResponse failed\n");
		return -1;
	}
}
