gecko-dev/dom/bluetooth/BluetoothOppManager.cpp
Gina Yeh 3643741917 Bug 913372 - Patch 2: Set mController to null before invoking callback function, r=echou
For each profile manager, we'd like to make sure that the data member of
mController is nulled out before invoking the callback function. Because, in the
callback function, we continue to handle the next connect/disconnect request,
and we'd like to null out mController of previous request before handling the
new one.
2013-10-28 12:07:01 +08:00

1506 lines
39 KiB
C++

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