michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "BluetoothOppManager.h" michael@0: michael@0: #include "BluetoothService.h" michael@0: #include "BluetoothSocket.h" michael@0: #include "BluetoothUtils.h" michael@0: #include "BluetoothUuid.h" michael@0: #include "ObexBase.h" michael@0: michael@0: #include "mozilla/dom/bluetooth/BluetoothTypes.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCExternalHandlerService.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIMIMEService.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIVolumeService.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #define TARGET_SUBDIR "Download/Bluetooth/" michael@0: michael@0: USING_BLUETOOTH_NAMESPACE michael@0: using namespace mozilla; michael@0: using namespace mozilla::ipc; michael@0: michael@0: namespace { michael@0: // Sending system message "bluetooth-opp-update-progress" every 50kb michael@0: static const uint32_t kUpdateProgressBase = 50 * 1024; michael@0: michael@0: /* michael@0: * The format of the header of an PUT request is michael@0: * [opcode:1][packet length:2][headerId:1][header length:2] michael@0: */ michael@0: static const uint32_t kPutRequestHeaderSize = 6; michael@0: michael@0: /* michael@0: * The format of the appended header of an PUT request is michael@0: * [headerId:1][header length:4] michael@0: * P.S. Length of name header is 4 since unicode is 2 bytes per char. michael@0: */ michael@0: static const uint32_t kPutRequestAppendHeaderSize = 5; michael@0: michael@0: StaticRefPtr sBluetoothOppManager; michael@0: static bool sInShutdown = false; michael@0: } michael@0: michael@0: class mozilla::dom::bluetooth::SendFileBatch { michael@0: public: michael@0: SendFileBatch(const nsAString& aDeviceAddress, nsIDOMBlob* aBlob) michael@0: : mDeviceAddress(aDeviceAddress) michael@0: { michael@0: mBlobs.AppendElement(aBlob); michael@0: } michael@0: michael@0: nsString mDeviceAddress; michael@0: nsCOMArray mBlobs; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: BluetoothOppManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(sBluetoothOppManager); michael@0: michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: HandleShutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: class SendSocketDataTask : public nsRunnable michael@0: { michael@0: public: michael@0: SendSocketDataTask(uint8_t* aStream, uint32_t aSize) michael@0: : mStream(aStream) michael@0: , mSize(aSize) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: sBluetoothOppManager->SendPutRequest(mStream, mSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsAutoArrayPtr mStream; michael@0: uint32_t mSize; michael@0: }; michael@0: michael@0: class ReadFileTask : public nsRunnable michael@0: { michael@0: public: michael@0: ReadFileTask(nsIInputStream* aInputStream, michael@0: uint32_t aRemoteMaxPacketSize) : mInputStream(aInputStream) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mAvailablePacketSize = aRemoteMaxPacketSize - kPutRequestHeaderSize; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: uint32_t numRead; michael@0: nsAutoArrayPtr buf(new char[mAvailablePacketSize]); michael@0: michael@0: // function inputstream->Read() only works on non-main thread michael@0: nsresult rv = mInputStream->Read(buf, mAvailablePacketSize, &numRead); michael@0: if (NS_FAILED(rv)) { michael@0: // Needs error handling here michael@0: BT_WARNING("Failed to read from input stream"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (numRead > 0) { michael@0: sBluetoothOppManager->CheckPutFinal(numRead); michael@0: michael@0: nsRefPtr task = michael@0: new SendSocketDataTask((uint8_t*)buf.forget(), numRead); michael@0: if (NS_FAILED(NS_DispatchToMainThread(task))) { michael@0: BT_WARNING("Failed to dispatch to main thread!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: }; michael@0: michael@0: private: michael@0: nsCOMPtr mInputStream; michael@0: uint32_t mAvailablePacketSize; michael@0: }; michael@0: michael@0: class CloseSocketTask : public Task michael@0: { michael@0: public: michael@0: CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket) michael@0: { michael@0: MOZ_ASSERT(aSocket); michael@0: } michael@0: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mSocket->CloseDroidSocket(); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mSocket; michael@0: }; michael@0: michael@0: BluetoothOppManager::BluetoothOppManager() : mConnected(false) michael@0: , mRemoteObexVersion(0) michael@0: , mRemoteConnectionFlags(0) michael@0: , mRemoteMaxPacketLength(0) michael@0: , mLastCommand(0) michael@0: , mPacketLength(0) michael@0: , mPutPacketReceivedLength(0) michael@0: , mBodySegmentLength(0) michael@0: , mAbortFlag(false) michael@0: , mNewFileFlag(false) michael@0: , mPutFinalFlag(false) michael@0: , mSendTransferCompleteFlag(false) michael@0: , mSuccessFlag(false) michael@0: , mIsServer(true) michael@0: , mWaitingForConfirmationFlag(false) michael@0: , mFileLength(0) michael@0: , mSentFileLength(0) michael@0: , mWaitingToSendPutFinal(false) michael@0: , mCurrentBlobIndex(-1) michael@0: { michael@0: mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); michael@0: } michael@0: michael@0: BluetoothOppManager::~BluetoothOppManager() michael@0: { michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { michael@0: BT_WARNING("Failed to remove shutdown observer!"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::Init() michael@0: { michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE(obs, false); michael@0: if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { michael@0: BT_WARNING("Failed to add shutdown observer!"); michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * We don't start listening here as BluetoothServiceBluedroid calls Listen() michael@0: * immediately when BT stops. michael@0: * michael@0: * If we start listening here, the listening fails when device boots up since michael@0: * Listen() is called again and restarts server socket. The restart causes michael@0: * absence of read events when device boots up. michael@0: */ michael@0: michael@0: return true; michael@0: } michael@0: michael@0: //static michael@0: BluetoothOppManager* michael@0: BluetoothOppManager::Get() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // If sBluetoothOppManager already exists, exit early michael@0: if (sBluetoothOppManager) { michael@0: return sBluetoothOppManager; michael@0: } michael@0: michael@0: // If we're in shutdown, don't create a new instance michael@0: NS_ENSURE_FALSE(sInShutdown, nullptr); michael@0: michael@0: // Create a new instance, register, and return michael@0: BluetoothOppManager *manager = new BluetoothOppManager(); michael@0: NS_ENSURE_TRUE(manager->Init(), nullptr); michael@0: michael@0: sBluetoothOppManager = manager; michael@0: return sBluetoothOppManager; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Stop listening because currently we only support one connection at a time. michael@0: if (mServerSocket) { michael@0: mServerSocket->Disconnect(); michael@0: mServerSocket = nullptr; michael@0: } michael@0: michael@0: mIsServer = false; michael@0: michael@0: BluetoothService* bs = BluetoothService::Get(); michael@0: if (!bs || sInShutdown || mSocket) { michael@0: OnSocketConnectError(mSocket); michael@0: return; michael@0: } michael@0: michael@0: mSocket = michael@0: new BluetoothSocket(this, BluetoothSocketType::RFCOMM, false, true); michael@0: mSocket->Connect(aDeviceAddress, -1); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::HandleShutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: sInShutdown = true; michael@0: Disconnect(nullptr); michael@0: sBluetoothOppManager = nullptr; michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::Listen() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mSocket) { michael@0: BT_WARNING("mSocket exists. Failed to listen."); michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Restart server socket since its underlying fd becomes invalid when michael@0: * BT stops; otherwise no more read events would be received even if michael@0: * BT restarts. michael@0: */ michael@0: if (mServerSocket) { michael@0: mServerSocket->Disconnect(); michael@0: mServerSocket = nullptr; michael@0: } michael@0: michael@0: mServerSocket = michael@0: new BluetoothSocket(this, BluetoothSocketType::RFCOMM, false, true); michael@0: michael@0: if (!mServerSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) { michael@0: BT_WARNING("[OPP] Can't listen on RFCOMM socket!"); michael@0: mServerSocket = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: mIsServer = true; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::StartSendingNextFile() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MOZ_ASSERT(!IsConnected()); michael@0: MOZ_ASSERT(!mBatches.IsEmpty()); michael@0: MOZ_ASSERT((int)mBatches[0].mBlobs.Length() > mCurrentBlobIndex + 1); michael@0: michael@0: mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; michael@0: michael@0: // Before sending content, we have to send a header including michael@0: // information such as file name, file length and content type. michael@0: ExtractBlobHeaders(); michael@0: StartFileTransfer(); michael@0: michael@0: if (mCurrentBlobIndex == 0) { michael@0: // We may have more than one file waiting for transferring, but only one michael@0: // CONNECT request would be sent. Therefore check if this is the very first michael@0: // file at the head of queue. michael@0: SendConnectRequest(); michael@0: } else { michael@0: SendPutHeaderRequest(mFileName, mFileLength); michael@0: AfterFirstPut(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::SendFile(const nsAString& aDeviceAddress, michael@0: BlobParent* aActor) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr blob = aActor->GetBlob(); michael@0: michael@0: return SendFile(aDeviceAddress, blob.get()); michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::SendFile(const nsAString& aDeviceAddress, michael@0: nsIDOMBlob* aBlob) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: AppendBlobToSend(aDeviceAddress, aBlob); michael@0: if (!mSocket) { michael@0: ProcessNextBatch(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::AppendBlobToSend(const nsAString& aDeviceAddress, michael@0: nsIDOMBlob* aBlob) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: int indexTail = mBatches.Length() - 1; michael@0: michael@0: /** michael@0: * Create a new batch if michael@0: * - mBatches is empty, or michael@0: * - aDeviceAddress differs from mDeviceAddress of the last batch michael@0: */ michael@0: if (mBatches.IsEmpty() || michael@0: aDeviceAddress != mBatches[indexTail].mDeviceAddress) { michael@0: SendFileBatch batch(aDeviceAddress, aBlob); michael@0: mBatches.AppendElement(batch); michael@0: } else { michael@0: mBatches[indexTail].mBlobs.AppendElement(aBlob); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::DiscardBlobsToSend() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MOZ_ASSERT(!mBatches.IsEmpty()); michael@0: MOZ_ASSERT(!mIsServer); michael@0: michael@0: int length = (int) mBatches[0].mBlobs.Length(); michael@0: while (length > mCurrentBlobIndex + 1) { michael@0: mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; michael@0: michael@0: BT_LOGR("idx %d", mCurrentBlobIndex); michael@0: ExtractBlobHeaders(); michael@0: StartFileTransfer(); michael@0: FileTransferComplete(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::ProcessNextBatch() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Remove the processed batch. michael@0: // A batch is processed if we've incremented mCurrentBlobIndex for it. michael@0: if (mCurrentBlobIndex >= 0) { michael@0: ClearQueue(); michael@0: mBatches.RemoveElementAt(0); michael@0: BT_LOGR("REMOVE. %d remaining", mBatches.Length()); michael@0: } michael@0: michael@0: // Process the next batch michael@0: if (!mBatches.IsEmpty()) { michael@0: ConnectInternal(mBatches[0].mDeviceAddress); michael@0: return true; michael@0: } michael@0: michael@0: // No more batch to process michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ClearQueue() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MOZ_ASSERT(!mIsServer); michael@0: MOZ_ASSERT(!mBatches.IsEmpty()); michael@0: MOZ_ASSERT(!mBatches[0].mBlobs.IsEmpty()); michael@0: michael@0: mCurrentBlobIndex = -1; michael@0: mBlob = nullptr; michael@0: mBatches[0].mBlobs.Clear(); michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::StopSendingFile() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mIsServer) { michael@0: mAbortFlag = true; michael@0: } else { michael@0: Disconnect(nullptr); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::ConfirmReceivingFile(bool aConfirm) michael@0: { michael@0: NS_ENSURE_TRUE(mConnected, false); michael@0: NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false); michael@0: michael@0: mWaitingForConfirmationFlag = false; michael@0: michael@0: // For the first packet of first file michael@0: bool success = false; michael@0: if (aConfirm) { michael@0: StartFileTransfer(); michael@0: if (CreateFile()) { michael@0: success = WriteToFile(mBodySegment.get(), mBodySegmentLength); michael@0: } michael@0: } michael@0: michael@0: if (success && mPutFinalFlag) { michael@0: mSuccessFlag = true; michael@0: FileTransferComplete(); michael@0: NotifyAboutFileChange(); michael@0: } michael@0: michael@0: ReplyToPut(mPutFinalFlag, success); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::AfterFirstPut() michael@0: { michael@0: mUpdateProgressCounter = 1; michael@0: mPutFinalFlag = false; michael@0: mPutPacketReceivedLength = 0; michael@0: mSentFileLength = 0; michael@0: mWaitingToSendPutFinal = false; michael@0: mSuccessFlag = false; michael@0: mBodySegmentLength = 0; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::AfterOppConnected() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mConnected = true; michael@0: mAbortFlag = false; michael@0: mWaitingForConfirmationFlag = true; michael@0: AfterFirstPut(); michael@0: // Get a mount lock to prevent the sdcard from being shared with michael@0: // the PC while we're doing a OPP file transfer. After OPP transaction michael@0: // were done, the mount lock will be freed. michael@0: if (!AcquireSdcardMountLock()) { michael@0: // If we fail to get a mount lock, abort this transaction michael@0: // Directly sending disconnect-request is better than abort-request michael@0: BT_WARNING("BluetoothOPPManager couldn't get a mount lock!"); michael@0: Disconnect(nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::AfterOppDisconnected() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mConnected = false; michael@0: mLastCommand = 0; michael@0: mPutPacketReceivedLength = 0; michael@0: mDsFile = nullptr; michael@0: michael@0: // We can't reset mSuccessFlag here since this function may be called michael@0: // before we send system message of transfer complete michael@0: // mSuccessFlag = false; michael@0: michael@0: if (mInputStream) { michael@0: mInputStream->Close(); michael@0: mInputStream = nullptr; michael@0: } michael@0: michael@0: if (mOutputStream) { michael@0: mOutputStream->Close(); michael@0: mOutputStream = nullptr; michael@0: } michael@0: michael@0: if (mReadFileThread) { michael@0: mReadFileThread->Shutdown(); michael@0: mReadFileThread = nullptr; michael@0: } michael@0: // Release the Mount lock if file transfer completed michael@0: if (mMountLock) { michael@0: // The mount lock will be implicitly unlocked michael@0: mMountLock = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::DeleteReceivedFile() michael@0: { michael@0: if (mOutputStream) { michael@0: mOutputStream->Close(); michael@0: mOutputStream = nullptr; michael@0: } michael@0: michael@0: if (mDsFile && mDsFile->mFile) { michael@0: mDsFile->mFile->Remove(false); michael@0: mDsFile = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::CreateFile() michael@0: { michael@0: MOZ_ASSERT(mPutPacketReceivedLength == mPacketLength); michael@0: michael@0: nsString path; michael@0: path.AssignLiteral(TARGET_SUBDIR); michael@0: path.Append(mFileName); michael@0: michael@0: mDsFile = DeviceStorageFile::CreateUnique( michael@0: path, nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_TRUE(mDsFile, false); michael@0: michael@0: nsCOMPtr f; michael@0: mDsFile->mFile->Clone(getter_AddRefs(f)); michael@0: michael@0: /* michael@0: * The function CreateUnique() may create a file with a different file michael@0: * name from the original mFileName. Therefore we have to retrieve michael@0: * the file name again. michael@0: */ michael@0: f->GetLeafName(mFileName); michael@0: michael@0: NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f); michael@0: NS_ENSURE_TRUE(mOutputStream, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength) michael@0: { michael@0: NS_ENSURE_TRUE(mOutputStream, false); michael@0: michael@0: uint32_t wrote = 0; michael@0: mOutputStream->Write((const char*)aData, aDataLength, &wrote); michael@0: NS_ENSURE_TRUE(aDataLength == (int) wrote, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Virtual function of class SocketConsumer michael@0: void michael@0: BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader) michael@0: { michael@0: if (aHeader.Has(ObexHeaderId::Name)) { michael@0: aHeader.GetName(mFileName); michael@0: } michael@0: michael@0: if (aHeader.Has(ObexHeaderId::Type)) { michael@0: aHeader.GetContentType(mContentType); michael@0: } michael@0: michael@0: if (aHeader.Has(ObexHeaderId::Length)) { michael@0: aHeader.GetLength(&mFileLength); michael@0: } michael@0: michael@0: if (aHeader.Has(ObexHeaderId::Body) || michael@0: aHeader.Has(ObexHeaderId::EndOfBody)) { michael@0: uint8_t* bodyPtr; michael@0: aHeader.GetBody(&bodyPtr); michael@0: mBodySegment = bodyPtr; michael@0: michael@0: aHeader.GetBodyLength(&mBodySegmentLength); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::ExtractBlobHeaders() michael@0: { michael@0: RetrieveSentFileName(); michael@0: michael@0: nsresult rv = mBlob->GetType(mContentType); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Can't get content type"); michael@0: SendDisconnectRequest(); michael@0: return false; michael@0: } michael@0: michael@0: uint64_t fileLength; michael@0: rv = mBlob->GetSize(&fileLength); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Can't get file size"); michael@0: SendDisconnectRequest(); michael@0: return false; michael@0: } michael@0: michael@0: // Currently we keep the size of files which were sent/received via michael@0: // Bluetooth not exceed UINT32_MAX because the Length header in OBEX michael@0: // is only 4-byte long. Although it is possible to transfer a file michael@0: // larger than UINT32_MAX, it needs to parse another OBEX Header michael@0: // and I would like to leave it as a feature. michael@0: if (fileLength > (uint64_t)UINT32_MAX) { michael@0: BT_WARNING("The file size is too large for now"); michael@0: SendDisconnectRequest(); michael@0: return false; michael@0: } michael@0: michael@0: mFileLength = fileLength; michael@0: rv = NS_NewThread(getter_AddRefs(mReadFileThread)); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Can't create thread"); michael@0: SendDisconnectRequest(); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::RetrieveSentFileName() michael@0: { michael@0: mFileName.Truncate(); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(mBlob); michael@0: if (file) { michael@0: file->GetName(mFileName); michael@0: } michael@0: michael@0: /** michael@0: * We try our best to get the file extention to avoid interoperability issues. michael@0: * However, once we found that we are unable to get suitable extension or michael@0: * information about the content type, sending a pre-defined file name without michael@0: * extension would be fine. michael@0: */ michael@0: if (mFileName.IsEmpty()) { michael@0: mFileName.AssignLiteral("Unknown"); michael@0: } michael@0: michael@0: int32_t offset = mFileName.RFindChar('/'); michael@0: if (offset != kNotFound) { michael@0: mFileName = Substring(mFileName, offset + 1); michael@0: } michael@0: michael@0: offset = mFileName.RFindChar('.'); michael@0: if (offset == kNotFound) { michael@0: nsCOMPtr mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID); michael@0: michael@0: if (mimeSvc) { michael@0: nsString mimeType; michael@0: mBlob->GetType(mimeType); michael@0: michael@0: nsCString extension; michael@0: nsresult rv = michael@0: mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), michael@0: EmptyCString(), michael@0: extension); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mFileName.AppendLiteral("."); michael@0: AppendUTF8toUTF16(extension, mFileName); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::IsReservedChar(char16_t c) michael@0: { michael@0: return (c < 0x0020 || michael@0: c == char16_t('?') || c == char16_t('|') || c == char16_t('<') || michael@0: c == char16_t('>') || c == char16_t('"') || c == char16_t(':') || michael@0: c == char16_t('/') || c == char16_t('*') || c == char16_t('\\')); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ValidateFileName() michael@0: { michael@0: int length = mFileName.Length(); michael@0: michael@0: for (int i = 0; i < length; ++i) { michael@0: // Replace reserved char of fat file system with '_' michael@0: if (IsReservedChar(mFileName.CharAt(i))) { michael@0: mFileName.Replace(i, 1, char16_t('_')); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aMessage); michael@0: michael@0: int frameHeaderLength = 0; michael@0: michael@0: // See if this is the first part of each Put packet michael@0: if (mPutPacketReceivedLength == 0) { michael@0: // Section 3.3.3 "Put", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: frameHeaderLength = 3; michael@0: michael@0: mPacketLength = ((((int)aMessage->mData[1]) << 8) | aMessage->mData[2]) - michael@0: frameHeaderLength; michael@0: /** michael@0: * A PUT request from remote devices may be divided into multiple parts. michael@0: * In other words, one request may need to be received multiple times, michael@0: * so here we keep a variable mPutPacketReceivedLength to indicate if michael@0: * current PUT request is done. michael@0: */ michael@0: mReceivedDataBuffer = new uint8_t[mPacketLength]; michael@0: mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal); michael@0: } michael@0: michael@0: int dataLength = aMessage->mSize - frameHeaderLength; michael@0: michael@0: // Check length before memcpy to prevent from memory pollution michael@0: if (dataLength < 0 || michael@0: mPutPacketReceivedLength + dataLength > mPacketLength) { michael@0: BT_LOGR("Received packet size is unreasonable"); michael@0: michael@0: ReplyToPut(mPutFinalFlag, false); michael@0: DeleteReceivedFile(); michael@0: FileTransferComplete(); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: memcpy(mReceivedDataBuffer.get() + mPutPacketReceivedLength, michael@0: &aMessage->mData[frameHeaderLength], dataLength); michael@0: michael@0: mPutPacketReceivedLength += dataLength; michael@0: michael@0: return (mPutPacketReceivedLength == mPacketLength); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: uint8_t opCode; michael@0: int receivedLength = aMessage->mSize; michael@0: michael@0: if (mPutPacketReceivedLength > 0) { michael@0: opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put; michael@0: } else { michael@0: opCode = aMessage->mData[0]; michael@0: michael@0: // When there's a Put packet right after a PutFinal packet, michael@0: // which means it's the start point of a new file. michael@0: if (mPutFinalFlag && michael@0: (opCode == ObexRequestCode::Put || michael@0: opCode == ObexRequestCode::PutFinal)) { michael@0: mNewFileFlag = true; michael@0: AfterFirstPut(); michael@0: } michael@0: } michael@0: michael@0: ObexHeaderSet pktHeaders(opCode); michael@0: if (opCode == ObexRequestCode::Connect) { michael@0: // Section 3.3.1 "Connect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] michael@0: // [Headers:var] michael@0: if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) { michael@0: ReplyError(ObexResponseCode::BadRequest); michael@0: return; michael@0: } michael@0: michael@0: ReplyToConnect(); michael@0: AfterOppConnected(); michael@0: } else if (opCode == ObexRequestCode::Abort) { michael@0: // Section 3.3.5 "Abort", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) { michael@0: ReplyError(ObexResponseCode::BadRequest); michael@0: return; michael@0: } michael@0: michael@0: ReplyToDisconnectOrAbort(); michael@0: DeleteReceivedFile(); michael@0: } else if (opCode == ObexRequestCode::Disconnect) { michael@0: // Section 3.3.2 "Disconnect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) { michael@0: ReplyError(ObexResponseCode::BadRequest); michael@0: return; michael@0: } michael@0: michael@0: ReplyToDisconnectOrAbort(); michael@0: AfterOppDisconnected(); michael@0: FileTransferComplete(); michael@0: } else if (opCode == ObexRequestCode::Put || michael@0: opCode == ObexRequestCode::PutFinal) { michael@0: if (!ComposePacket(opCode, aMessage)) { michael@0: return; michael@0: } michael@0: michael@0: // A Put packet is received completely michael@0: ParseHeaders(mReceivedDataBuffer.get(), michael@0: mPutPacketReceivedLength, &pktHeaders); michael@0: ExtractPacketHeaders(pktHeaders); michael@0: ValidateFileName(); michael@0: michael@0: // When we cancel the transfer, delete the file and notify completion michael@0: if (mAbortFlag) { michael@0: ReplyToPut(mPutFinalFlag, false); michael@0: mSentFileLength += mBodySegmentLength; michael@0: DeleteReceivedFile(); michael@0: FileTransferComplete(); michael@0: return; michael@0: } michael@0: michael@0: // Wait until get confirmation from user, then create file and write to it michael@0: if (mWaitingForConfirmationFlag) { michael@0: ReceivingFileConfirmation(); michael@0: mSentFileLength += mBodySegmentLength; michael@0: return; michael@0: } michael@0: michael@0: // Already get confirmation from user, create a new file if needed and michael@0: // write to output stream michael@0: if (mNewFileFlag) { michael@0: StartFileTransfer(); michael@0: if (!CreateFile()) { michael@0: ReplyToPut(mPutFinalFlag, false); michael@0: return; michael@0: } michael@0: mNewFileFlag = false; michael@0: } michael@0: michael@0: if (!WriteToFile(mBodySegment.get(), mBodySegmentLength)) { michael@0: ReplyToPut(mPutFinalFlag, false); michael@0: return; michael@0: } michael@0: michael@0: ReplyToPut(mPutFinalFlag, true); michael@0: michael@0: // Send progress update michael@0: mSentFileLength += mBodySegmentLength; michael@0: if (mSentFileLength > kUpdateProgressBase * mUpdateProgressCounter) { michael@0: UpdateProgress(); michael@0: mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; michael@0: } michael@0: michael@0: // Success to receive a file and notify completion michael@0: if (mPutFinalFlag) { michael@0: mSuccessFlag = true; michael@0: FileTransferComplete(); michael@0: NotifyAboutFileChange(); michael@0: } michael@0: } else if (opCode == ObexRequestCode::Get || michael@0: opCode == ObexRequestCode::GetFinal || michael@0: opCode == ObexRequestCode::SetPath) { michael@0: ReplyError(ObexResponseCode::BadRequest); michael@0: BT_WARNING("Unsupported ObexRequestCode"); michael@0: } else { michael@0: ReplyError(ObexResponseCode::NotImplemented); michael@0: BT_WARNING("Unrecognized ObexRequestCode"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: uint8_t opCode = aMessage->mData[0]; michael@0: michael@0: // Check response code and send out system message as finished if the response michael@0: // code is somehow incorrect. michael@0: uint8_t expectedOpCode = ObexResponseCode::Success; michael@0: if (mLastCommand == ObexRequestCode::Put) { michael@0: expectedOpCode = ObexResponseCode::Continue; michael@0: } michael@0: michael@0: if (opCode != expectedOpCode) { michael@0: if (mLastCommand == ObexRequestCode::Put || michael@0: mLastCommand == ObexRequestCode::Abort || michael@0: mLastCommand == ObexRequestCode::PutFinal) { michael@0: SendDisconnectRequest(); michael@0: } michael@0: nsAutoCString str; michael@0: str += "[OPP] 0x"; michael@0: str.AppendInt(mLastCommand, 16); michael@0: str += " failed"; michael@0: BT_WARNING(str.get()); michael@0: FileTransferComplete(); michael@0: return; michael@0: } michael@0: michael@0: if (mLastCommand == ObexRequestCode::PutFinal) { michael@0: mSuccessFlag = true; michael@0: FileTransferComplete(); michael@0: michael@0: if (mInputStream) { michael@0: mInputStream->Close(); michael@0: mInputStream = nullptr; michael@0: } michael@0: michael@0: if (mCurrentBlobIndex + 1 == (int) mBatches[0].mBlobs.Length()) { michael@0: SendDisconnectRequest(); michael@0: } else { michael@0: StartSendingNextFile(); michael@0: } michael@0: } else if (mLastCommand == ObexRequestCode::Abort) { michael@0: SendDisconnectRequest(); michael@0: FileTransferComplete(); michael@0: } else if (mLastCommand == ObexRequestCode::Disconnect) { michael@0: AfterOppDisconnected(); michael@0: // Most devices will directly terminate connection after receiving michael@0: // Disconnect request, so we make a delay here. If the socket hasn't been michael@0: // disconnected, we will close it. michael@0: if (mSocket) { michael@0: MessageLoop::current()-> michael@0: PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000); michael@0: } michael@0: } else if (mLastCommand == ObexRequestCode::Connect) { michael@0: MOZ_ASSERT(!mFileName.IsEmpty()); michael@0: MOZ_ASSERT(mBlob); michael@0: michael@0: AfterOppConnected(); michael@0: michael@0: // Keep remote information michael@0: mRemoteObexVersion = aMessage->mData[3]; michael@0: mRemoteConnectionFlags = aMessage->mData[4]; michael@0: mRemoteMaxPacketLength = michael@0: (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]); michael@0: michael@0: // The length of file name exceeds maximum length. michael@0: int fileNameByteLen = (mFileName.Length() + 1) * 2; michael@0: int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize; michael@0: if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) { michael@0: BT_WARNING("The length of file name is aberrant."); michael@0: SendDisconnectRequest(); michael@0: return; michael@0: } michael@0: michael@0: SendPutHeaderRequest(mFileName, mFileLength); michael@0: } else if (mLastCommand == ObexRequestCode::Put) { michael@0: if (mWaitingToSendPutFinal) { michael@0: SendPutFinalRequest(); michael@0: return; michael@0: } michael@0: michael@0: if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) { michael@0: UpdateProgress(); michael@0: mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (!mInputStream) { michael@0: rv = mBlob->GetInternalStream(getter_AddRefs(mInputStream)); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Can't get internal stream of blob"); michael@0: SendDisconnectRequest(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr task = new ReadFileTask(mInputStream, michael@0: mRemoteMaxPacketLength); michael@0: rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Cannot dispatch read file task!"); michael@0: SendDisconnectRequest(); michael@0: } michael@0: } else { michael@0: BT_WARNING("Unhandled ObexRequestCode"); michael@0: } michael@0: } michael@0: michael@0: // Virtual function of class SocketConsumer michael@0: void michael@0: BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket, michael@0: nsAutoPtr& aMessage) michael@0: { michael@0: if (mIsServer) { michael@0: ServerDataHandler(aMessage); michael@0: } else { michael@0: ClientDataHandler(aMessage); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendConnectRequest() michael@0: { michael@0: if (mConnected) return; michael@0: michael@0: // Section 3.3.1 "Connect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] michael@0: // [Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 7; michael@0: michael@0: req[3] = 0x10; // version=1.0 michael@0: req[4] = 0x00; // flag=0x00 michael@0: req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; michael@0: req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; michael@0: michael@0: SendObexData(req, ObexRequestCode::Connect, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName, michael@0: int aFileSize) michael@0: { michael@0: if (!mConnected) return; michael@0: michael@0: uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; michael@0: michael@0: int len = aFileName.Length(); michael@0: uint8_t* fileName = new uint8_t[(len + 1) * 2]; michael@0: const char16_t* fileNamePtr = aFileName.BeginReading(); michael@0: michael@0: for (int i = 0; i < len; i++) { michael@0: fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8); michael@0: fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i]; michael@0: } michael@0: michael@0: fileName[len * 2] = 0x00; michael@0: fileName[len * 2 + 1] = 0x00; michael@0: michael@0: int index = 3; michael@0: index += AppendHeaderName(&req[index], mRemoteMaxPacketLength - index, michael@0: (char*)fileName, (len + 1) * 2); michael@0: index += AppendHeaderLength(&req[index], aFileSize); michael@0: michael@0: SendObexData(req, ObexRequestCode::Put, index); michael@0: michael@0: delete [] fileName; michael@0: delete [] req; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendPutRequest(uint8_t* aFileBody, michael@0: int aFileBodyLength) michael@0: { michael@0: if (!mConnected) return; michael@0: int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize; michael@0: if (aFileBodyLength > packetLeftSpace) { michael@0: BT_WARNING("Not allowed such a small MaxPacketLength value"); michael@0: return; michael@0: } michael@0: michael@0: // Section 3.3.3 "Put", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; michael@0: michael@0: int index = 3; michael@0: index += AppendHeaderBody(&req[index], mRemoteMaxPacketLength - index, michael@0: aFileBody, aFileBodyLength); michael@0: michael@0: SendObexData(req, ObexRequestCode::Put, index); michael@0: delete [] req; michael@0: michael@0: mSentFileLength += aFileBodyLength; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendPutFinalRequest() michael@0: { michael@0: if (!mConnected) return; michael@0: michael@0: /** michael@0: * Section 2.2.9, "End-of-Body", IrObex 1.2 michael@0: * End-of-Body is used to identify the last chunk of the object body. michael@0: * For most platforms, a PutFinal packet is sent with an zero length michael@0: * End-of-Body header. michael@0: */ michael@0: michael@0: // [opcode:1][length:2] michael@0: int index = 3; michael@0: uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; michael@0: index += AppendHeaderEndOfBody(&req[index]); michael@0: michael@0: SendObexData(req, ObexRequestCode::PutFinal, index); michael@0: delete [] req; michael@0: michael@0: mWaitingToSendPutFinal = false; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendDisconnectRequest() michael@0: { michael@0: if (!mConnected) return; michael@0: michael@0: // Section 3.3.2 "Disconnect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 3; michael@0: michael@0: SendObexData(req, ObexRequestCode::Disconnect, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::CheckPutFinal(uint32_t aNumRead) michael@0: { michael@0: if (mSentFileLength + aNumRead >= mFileLength) { michael@0: mWaitingToSendPutFinal = true; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothOppManager::IsConnected() michael@0: { michael@0: return (mConnected && !mSendTransferCompleteFlag); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::GetAddress(nsAString& aDeviceAddress) michael@0: { michael@0: return mSocket->GetAddress(aDeviceAddress); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ReplyToConnect() michael@0: { michael@0: if (mConnected) return; michael@0: michael@0: // Section 3.3.1 "Connect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] michael@0: // [Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 7; michael@0: michael@0: req[3] = 0x10; // version=1.0 michael@0: req[4] = 0x00; // flag=0x00 michael@0: req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; michael@0: req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; michael@0: michael@0: SendObexData(req, ObexResponseCode::Success, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ReplyToDisconnectOrAbort() michael@0: { michael@0: if (!mConnected) return; michael@0: michael@0: // Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2 michael@0: // The format of response packet of "Disconnect" and "Abort" are the same michael@0: // [opcode:1][length:2][Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 3; michael@0: michael@0: SendObexData(req, ObexResponseCode::Success, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue) michael@0: { michael@0: if (!mConnected) return; michael@0: michael@0: // The received length can be reset here because this is where we reply to a michael@0: // complete put packet. michael@0: mPutPacketReceivedLength = 0; michael@0: michael@0: // Section 3.3.2 "Disconnect", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 3; michael@0: uint8_t opcode; michael@0: michael@0: if (aContinue) { michael@0: opcode = (aFinal)? ObexResponseCode::Success : michael@0: ObexResponseCode::Continue; michael@0: } else { michael@0: opcode = (aFinal)? ObexResponseCode::Unauthorized : michael@0: ObexResponseCode::Unauthorized & (~FINAL_BIT); michael@0: } michael@0: michael@0: SendObexData(req, opcode, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ReplyError(uint8_t aError) michael@0: { michael@0: BT_LOGR("error: %d", aError); michael@0: michael@0: // Section 3.2 "Response Format", IrOBEX 1.2 michael@0: // [opcode:1][length:2][Headers:var] michael@0: uint8_t req[255]; michael@0: int index = 3; michael@0: michael@0: SendObexData(req, aError, index); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize) michael@0: { michael@0: SetObexPacketInfo(aData, aOpcode, aSize); michael@0: michael@0: if (!mIsServer) { michael@0: mLastCommand = aOpcode; michael@0: } michael@0: michael@0: UnixSocketRawData* s = new UnixSocketRawData(aSize); michael@0: memcpy(s->mData, aData, s->mSize); michael@0: mSocket->SendDroidSocketData(s); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::FileTransferComplete() michael@0: { michael@0: if (mSendTransferCompleteFlag) { michael@0: return; michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-complete"); michael@0: InfallibleTArray parameters; michael@0: michael@0: BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); michael@0: BT_APPEND_NAMED_VALUE(parameters, "success", mSuccessFlag); michael@0: BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileLength", mSentFileLength); michael@0: BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); michael@0: michael@0: BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); michael@0: michael@0: mSendTransferCompleteFlag = true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::StartFileTransfer() michael@0: { michael@0: NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-start"); michael@0: InfallibleTArray parameters; michael@0: michael@0: BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); michael@0: BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); michael@0: BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); michael@0: michael@0: BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); michael@0: michael@0: mSendTransferCompleteFlag = false; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::UpdateProgress() michael@0: { michael@0: NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-update-progress"); michael@0: InfallibleTArray parameters; michael@0: michael@0: BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); michael@0: BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); michael@0: BT_APPEND_NAMED_VALUE(parameters, "processedLength", mSentFileLength); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); michael@0: michael@0: BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::ReceivingFileConfirmation() michael@0: { michael@0: NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-receiving-file-confirmation"); michael@0: InfallibleTArray parameters; michael@0: michael@0: BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); michael@0: BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); michael@0: BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); michael@0: michael@0: BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::NotifyAboutFileChange() michael@0: { michael@0: NS_NAMED_LITERAL_STRING(data, "modified"); michael@0: michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: michael@0: obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get()); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket) michael@0: { michael@0: BT_LOGR("[%s]", (mIsServer)? "server" : "client"); michael@0: MOZ_ASSERT(aSocket); michael@0: michael@0: /** michael@0: * If the created connection is an inbound connection, close server socket michael@0: * because currently only one file-transfer session is allowed. After that, michael@0: * we need to make sure that server socket would be nulled out. michael@0: * As for outbound connections, we just notify the controller that it's done. michael@0: */ michael@0: if (aSocket == mServerSocket) { michael@0: MOZ_ASSERT(!mSocket); michael@0: mServerSocket.swap(mSocket); michael@0: } michael@0: michael@0: // Cache device address since we can't get socket address when a remote michael@0: // device disconnect with us. michael@0: mSocket->GetAddress(mDeviceAddress); michael@0: michael@0: // Start sending file if we connect as a client michael@0: if (!mIsServer) { michael@0: StartSendingNextFile(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket) michael@0: { michael@0: BT_LOGR("[%s]", (mIsServer)? "server" : "client"); michael@0: michael@0: mServerSocket = nullptr; michael@0: mSocket = nullptr; michael@0: michael@0: if (!mIsServer) { michael@0: // Inform gaia of remaining blobs' sending failure michael@0: DiscardBlobsToSend(); michael@0: } michael@0: michael@0: // Listen as a server if there's no more batch to process michael@0: if (!ProcessNextBatch()) { michael@0: Listen(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) michael@0: { michael@0: MOZ_ASSERT(aSocket); michael@0: if (aSocket != mSocket) { michael@0: // Do nothing when a listening server socket is closed. michael@0: return; michael@0: } michael@0: BT_LOGR("[%s]", (mIsServer) ? "server" : "client"); michael@0: michael@0: /** michael@0: * It is valid for a bluetooth device which is transfering file via OPP michael@0: * closing socket without sending OBEX disconnect request first. So we michael@0: * delete the broken file when we failed to receive a file from the remote, michael@0: * and notify the transfer has been completed (but failed). We also call michael@0: * AfterOppDisconnected here to ensure all variables will be cleaned. michael@0: */ michael@0: if (!mSuccessFlag) { michael@0: if (mIsServer) { michael@0: DeleteReceivedFile(); michael@0: } michael@0: michael@0: FileTransferComplete(); michael@0: if (!mIsServer) { michael@0: // Inform gaia of remaining blobs' sending failure michael@0: DiscardBlobsToSend(); michael@0: } michael@0: } michael@0: michael@0: AfterOppDisconnected(); michael@0: mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); michael@0: mSuccessFlag = false; michael@0: michael@0: mSocket = nullptr; michael@0: // Listen as a server if there's no more batch to process michael@0: if (!ProcessNextBatch()) { michael@0: Listen(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::Disconnect(BluetoothProfileController* aController) michael@0: { michael@0: if (mSocket) { michael@0: mSocket->Disconnect(); michael@0: } else { michael@0: BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothOppManager, nsIObserver) michael@0: michael@0: bool michael@0: BluetoothOppManager::AcquireSdcardMountLock() michael@0: { michael@0: nsCOMPtr volumeSrv = michael@0: do_GetService(NS_VOLUMESERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(volumeSrv, false); michael@0: nsresult rv; michael@0: rv = volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"), michael@0: getter_AddRefs(mMountLock)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress, michael@0: const nsAString& aServiceUuid, michael@0: int aChannel) michael@0: { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) michael@0: { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::Connect(const nsAString& aDeviceAddress, michael@0: BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnConnect(const nsAString& aErrorStr) michael@0: { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr) michael@0: { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothOppManager::Reset() michael@0: { michael@0: MOZ_ASSERT(false); michael@0: }