mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
1761 lines
46 KiB
C++
1761 lines
46 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=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 "BluetoothService.h"
|
|
#include "BluetoothSocket.h"
|
|
#include "BluetoothUtils.h"
|
|
#include "BluetoothUuid.h"
|
|
#include "ObexBase.h"
|
|
|
|
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
|
|
#include "mozilla/dom/ipc/BlobParent.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCExternalHandlerService.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.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::dom;
|
|
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;
|
|
|
|
/*
|
|
* The format of the appended header of an PUT request is
|
|
* [headerId:1][header length:4]
|
|
* P.S. Length of name header is 4 since unicode is 2 bytes per char.
|
|
*/
|
|
static const uint32_t kPutRequestAppendHeaderSize = 5;
|
|
|
|
// The default timeout we permit to wait for SDP updating if we can't get
|
|
// service channel.
|
|
static const double kSdpUpdatingTimeoutMs = 3000.0;
|
|
|
|
// UUID of OBEX Object Push
|
|
static const BluetoothUuid kObexObjectPush(OBJECT_PUSH);
|
|
|
|
StaticRefPtr<BluetoothOppManager> sBluetoothOppManager;
|
|
static bool sInShutdown = false;
|
|
}
|
|
|
|
BEGIN_BLUETOOTH_NAMESPACE
|
|
|
|
class BluetoothOppManager::SendFileBatch final
|
|
{
|
|
public:
|
|
SendFileBatch(const BluetoothAddress& aDeviceAddress, Blob* aBlob)
|
|
: mDeviceAddress(aDeviceAddress)
|
|
{
|
|
mBlobs.AppendElement(aBlob);
|
|
}
|
|
|
|
BluetoothAddress mDeviceAddress;
|
|
nsTArray<RefPtr<Blob>> mBlobs;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
BluetoothOppManager::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
MOZ_ASSERT(sBluetoothOppManager);
|
|
|
|
if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
|
|
HandleVolumeStateChanged(aSubject);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
HandleShutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
class BluetoothOppManager::SendSocketDataTask final : public nsRunnable
|
|
{
|
|
public:
|
|
SendSocketDataTask(UniquePtr<uint8_t[]> aStream, uint32_t aSize)
|
|
: mStream(Move(aStream))
|
|
, mSize(aSize)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
sBluetoothOppManager->SendPutRequest(mStream.get(), mSize);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
UniquePtr<uint8_t[]> mStream;
|
|
uint32_t mSize;
|
|
};
|
|
|
|
class BluetoothOppManager::ReadFileTask final : 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;
|
|
auto buf = MakeUnique<uint8_t[]>(mAvailablePacketSize);
|
|
|
|
// function inputstream->Read() only works on non-main thread
|
|
nsresult rv = mInputStream->Read((char*)buf.get(), 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);
|
|
|
|
RefPtr<SendSocketDataTask> task =
|
|
new SendSocketDataTask(Move(buf), 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 BluetoothOppManager::CloseSocketTask final : public Task
|
|
{
|
|
public:
|
|
CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
}
|
|
|
|
void Run() override
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mSocket->Close();
|
|
}
|
|
|
|
private:
|
|
RefPtr<BluetoothSocket> mSocket;
|
|
};
|
|
|
|
BluetoothOppManager::BluetoothOppManager() : mConnected(false)
|
|
, mRemoteObexVersion(0)
|
|
, mRemoteConnectionFlags(0)
|
|
, mRemoteMaxPacketLength(0)
|
|
, mLastCommand(0)
|
|
, mPacketLength(0)
|
|
, mPutPacketReceivedLength(0)
|
|
, mBodySegmentLength(0)
|
|
, mNeedsUpdatingSdpRecords(false)
|
|
, mAbortFlag(false)
|
|
, mNewFileFlag(false)
|
|
, mPutFinalFlag(false)
|
|
, mSendTransferCompleteFlag(false)
|
|
, mSuccessFlag(false)
|
|
, mIsServer(true)
|
|
, mWaitingForConfirmationFlag(false)
|
|
, mFileLength(0)
|
|
, mSentFileLength(0)
|
|
, mWaitingToSendPutFinal(false)
|
|
, mCurrentBlobIndex(-1)
|
|
{ }
|
|
|
|
BluetoothOppManager::~BluetoothOppManager()
|
|
{ }
|
|
|
|
nsresult
|
|
BluetoothOppManager::Init()
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
auto rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Failed to add shutdown observer!");
|
|
return rv;
|
|
}
|
|
|
|
rv = obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Failed to add ns volume observer!");
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* We don't start listening here as BluetoothServiceBluedroid calls Listen()
|
|
* immediately when BT stops.
|
|
*
|
|
* If we start listening here, the listening fails when device boots up since
|
|
* Listen() is called again and restarts server socket. The restart causes
|
|
* absence of read events when device boots up.
|
|
*/
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::Uninit()
|
|
{
|
|
if (mServerSocket) {
|
|
mServerSocket->SetObserver(nullptr);
|
|
|
|
if (mServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mServerSocket->Close();
|
|
}
|
|
mServerSocket = nullptr;
|
|
}
|
|
|
|
if (mSocket) {
|
|
mSocket->SetObserver(nullptr);
|
|
|
|
if (mSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mSocket->Close();
|
|
}
|
|
mSocket = nullptr;
|
|
}
|
|
|
|
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!");
|
|
}
|
|
|
|
if (NS_FAILED(obs->RemoveObserver(this, NS_VOLUME_STATE_CHANGED))) {
|
|
BT_WARNING("Failed to remove volume observer!");
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
BluetoothOppManager::InitOppInterface(BluetoothProfileResultHandler* aRes)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aRes) {
|
|
aRes->Init();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
BluetoothOppManager::DeinitOppInterface(BluetoothProfileResultHandler* aRes)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (sBluetoothOppManager) {
|
|
sBluetoothOppManager->Uninit();
|
|
sBluetoothOppManager = nullptr;
|
|
}
|
|
|
|
if (aRes) {
|
|
aRes->Deinit();
|
|
}
|
|
}
|
|
|
|
//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
|
|
RefPtr<BluetoothOppManager> manager = new BluetoothOppManager();
|
|
NS_ENSURE_SUCCESS(manager->Init(), nullptr);
|
|
|
|
sBluetoothOppManager = manager;
|
|
|
|
return sBluetoothOppManager;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ConnectInternal(const BluetoothAddress& aDeviceAddress)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Stop listening because currently we only support one connection at a time.
|
|
if (mServerSocket &&
|
|
mServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mServerSocket->Close();
|
|
}
|
|
mServerSocket = nullptr;
|
|
|
|
mIsServer = false;
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
if (!bs || sInShutdown || mSocket) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
|
|
mNeedsUpdatingSdpRecords = true;
|
|
|
|
if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, kObexObjectPush, this))) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
|
|
mSocket = new BluetoothSocket(this);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::HandleShutdown()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
sInShutdown = true;
|
|
Disconnect(nullptr);
|
|
Uninit();
|
|
|
|
sBluetoothOppManager = nullptr;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::HandleVolumeStateChanged(nsISupports* aSubject)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mConnected) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Disconnect ongoing OPP connection when a volume becomes non-mounted, in
|
|
* case files of transfer are located on that volume. |OnSocketDisconnect|
|
|
* will handle incomplete file transfers.
|
|
*/
|
|
|
|
nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
|
|
if (!vol) {
|
|
return;
|
|
}
|
|
|
|
int32_t state;
|
|
vol->GetState(&state);
|
|
if (nsIVolume::STATE_MOUNTED != state) {
|
|
BT_LOGR("Volume becomes non-mounted. Abort ongoing OPP connection");
|
|
Disconnect(nullptr);
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::Listen()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mSocket) {
|
|
BT_WARNING("mSocket exists. Failed to listen.");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Restart server socket since its underlying fd becomes invalid when
|
|
* BT stops; otherwise no more read events would be received even if
|
|
* BT restarts.
|
|
*/
|
|
if (mServerSocket &&
|
|
mServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mServerSocket->Close();
|
|
}
|
|
mServerSocket = nullptr;
|
|
|
|
mServerSocket = new BluetoothSocket(this);
|
|
|
|
nsresult rv = mServerSocket->Listen(NS_LITERAL_STRING("OBEX Object Push"),
|
|
kObexObjectPush,
|
|
BluetoothSocketType::RFCOMM,
|
|
BluetoothReservedChannels::CHANNEL_OPUSH,
|
|
false, true);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("[OPP] Can't listen on RFCOMM socket!");
|
|
mServerSocket = nullptr;
|
|
return false;
|
|
}
|
|
|
|
mIsServer = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::StartSendingNextFile()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(!mBatches.IsEmpty());
|
|
MOZ_ASSERT((int)mBatches[0].mBlobs.Length() > mCurrentBlobIndex + 1);
|
|
|
|
mBlob = mBatches[0].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 BluetoothAddress& aDeviceAddress,
|
|
BlobParent* aActor)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<BlobImpl> impl = aActor->GetBlobImpl();
|
|
RefPtr<Blob> blob = Blob::Create(nullptr, impl);
|
|
|
|
return SendFile(aDeviceAddress, blob.get());
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::SendFile(const BluetoothAddress& aDeviceAddress,
|
|
Blob* aBlob)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
AppendBlobToSend(aDeviceAddress, aBlob);
|
|
if (!mSocket) {
|
|
ProcessNextBatch();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::AppendBlobToSend(const BluetoothAddress& aDeviceAddress,
|
|
Blob* aBlob)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
int indexTail = mBatches.Length() - 1;
|
|
|
|
/**
|
|
* Create a new batch if
|
|
* - mBatches is empty, or
|
|
* - aDeviceAddress differs from mDeviceAddress of the last batch
|
|
*/
|
|
if (mBatches.IsEmpty() ||
|
|
aDeviceAddress != mBatches[indexTail].mDeviceAddress) {
|
|
SendFileBatch batch(aDeviceAddress, aBlob);
|
|
mBatches.AppendElement(batch);
|
|
} else {
|
|
mBatches[indexTail].mBlobs.AppendElement(aBlob);
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::DiscardBlobsToSend()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(!mBatches.IsEmpty());
|
|
MOZ_ASSERT(!mIsServer);
|
|
|
|
int length = (int) mBatches[0].mBlobs.Length();
|
|
while (length > mCurrentBlobIndex + 1) {
|
|
mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex];
|
|
|
|
BT_LOGR("idx %d", mCurrentBlobIndex);
|
|
ExtractBlobHeaders();
|
|
StartFileTransfer();
|
|
FileTransferComplete();
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::ProcessNextBatch()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!IsConnected());
|
|
|
|
// Remove the processed batch.
|
|
// A batch is processed if we've incremented mCurrentBlobIndex for it.
|
|
if (mCurrentBlobIndex >= 0) {
|
|
ClearQueue();
|
|
mBatches.RemoveElementAt(0);
|
|
BT_LOGR("REMOVE. %d remaining", mBatches.Length());
|
|
}
|
|
|
|
// Process the next batch
|
|
if (!mBatches.IsEmpty()) {
|
|
ConnectInternal(mBatches[0].mDeviceAddress);
|
|
return true;
|
|
}
|
|
|
|
// No more batch to process
|
|
return false;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ClearQueue()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(!mIsServer);
|
|
MOZ_ASSERT(!mBatches.IsEmpty());
|
|
MOZ_ASSERT(!mBatches[0].mBlobs.IsEmpty());
|
|
|
|
mCurrentBlobIndex = -1;
|
|
mBlob = nullptr;
|
|
mBatches[0].mBlobs.Clear();
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
RestoreReceivedFileAndNotify();
|
|
FileTransferComplete();
|
|
}
|
|
|
|
ReplyToPut(mPutFinalFlag, success);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::AfterFirstPut()
|
|
{
|
|
mUpdateProgressCounter = 1;
|
|
mPutFinalFlag = false;
|
|
mPutPacketReceivedLength = 0;
|
|
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;
|
|
mLastCommand = 0;
|
|
mPutPacketReceivedLength = 0;
|
|
mDsFile = nullptr;
|
|
mDummyDsFile = nullptr;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Store local pointer of |mReadFileThread| to avoid shutdown reentry crash
|
|
// See bug 1191715 comment 19 for more details.
|
|
nsCOMPtr<nsIThread> thread = mReadFileThread.forget();
|
|
if (thread) {
|
|
thread->Shutdown();
|
|
}
|
|
|
|
// Release the mount lock if file transfer completed
|
|
if (mMountLock) {
|
|
// The mount lock will be implicitly unlocked
|
|
mMountLock = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::RestoreReceivedFileAndNotify()
|
|
{
|
|
// Remove the empty dummy file
|
|
if (mDummyDsFile && mDummyDsFile->mFile) {
|
|
mDummyDsFile->mFile->Remove(false);
|
|
mDummyDsFile = nullptr;
|
|
}
|
|
|
|
// Remove the trailing ".part" file name from mDsFile by two steps
|
|
// 1. mDsFile->SetPath() so that the notification sent to Gaia will carry
|
|
// correct information of the file.
|
|
// 2. mDsFile->mFile->RenameTo() so that the file name would actually be
|
|
// changed in file system.
|
|
if (mDsFile && mDsFile->mFile) {
|
|
nsString path;
|
|
path.AssignLiteral(TARGET_SUBDIR);
|
|
path.Append(mFileName);
|
|
|
|
mDsFile->SetPath(path);
|
|
mDsFile->mFile->RenameTo(nullptr, mFileName);
|
|
}
|
|
|
|
// Notify about change of received file
|
|
NotifyAboutFileChange();
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::DeleteReceivedFile()
|
|
{
|
|
if (mOutputStream) {
|
|
mOutputStream->Close();
|
|
mOutputStream = nullptr;
|
|
}
|
|
|
|
if (mDsFile && mDsFile->mFile) {
|
|
mDsFile->mFile->Remove(false);
|
|
mDsFile = nullptr;
|
|
}
|
|
|
|
// Remove the empty dummy file
|
|
if (mDummyDsFile && mDummyDsFile->mFile) {
|
|
mDummyDsFile->mFile->Remove(false);
|
|
mDummyDsFile = nullptr;
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::CreateFile()
|
|
{
|
|
MOZ_ASSERT(mPutPacketReceivedLength == mPacketLength);
|
|
|
|
// Create one dummy file to be a placeholder for the target file name, and
|
|
// create another file with a meaningless file extension to write the received
|
|
// data. By doing this, we can prevent applications from parsing incomplete
|
|
// data in the middle of the receiving process.
|
|
nsString path;
|
|
path.AssignLiteral(TARGET_SUBDIR);
|
|
path.Append(mFileName);
|
|
|
|
// Use an empty dummy file object to occupy the file name, so that after the
|
|
// whole file has been received successfully by using mDsFile, we could just
|
|
// remove mDummyDsFile and rename mDsFile to the file name of mDummyDsFile.
|
|
mDummyDsFile =
|
|
DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
NS_ENSURE_TRUE(mDummyDsFile, false);
|
|
|
|
// 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.
|
|
mDummyDsFile->mFile->GetLeafName(mFileName);
|
|
|
|
BT_LOGR("mFileName: %s", NS_ConvertUTF16toUTF8(mFileName).get());
|
|
|
|
// Prepare the entire file path for the .part file
|
|
path.Truncate();
|
|
path.AssignLiteral(TARGET_SUBDIR);
|
|
path.Append(mFileName);
|
|
path.AppendLiteral(".part");
|
|
|
|
mDsFile =
|
|
DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
NS_ENSURE_TRUE(mDsFile, false);
|
|
|
|
NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mDsFile->mFile);
|
|
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 == (int) 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, &mBodySegmentLength);
|
|
mBodySegment = bodyPtr;
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::ExtractBlobHeaders()
|
|
{
|
|
RetrieveSentFileName();
|
|
mBlob->GetType(mContentType);
|
|
|
|
ErrorResult rv;
|
|
uint64_t fileLength = mBlob->GetSize(rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
SendDisconnectRequest();
|
|
rv.SuppressException();
|
|
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_WARN_IF(rv.Failed())) {
|
|
SendDisconnectRequest();
|
|
rv.SuppressException();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::RetrieveSentFileName()
|
|
{
|
|
mFileName.Truncate();
|
|
|
|
RefPtr<File> file = static_cast<Blob*>(mBlob.get())->ToFile();
|
|
if (file) {
|
|
file->GetName(mFileName);
|
|
}
|
|
|
|
/**
|
|
* We try our best to get the file extension 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.Append('.');
|
|
AppendUTF8toUTF16(extension, mFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::IsReservedChar(char16_t c)
|
|
{
|
|
return (c < 0x0020 ||
|
|
c == char16_t('?') || c == char16_t('|') || c == char16_t('<') ||
|
|
c == char16_t('>') || c == char16_t('"') || c == char16_t(':') ||
|
|
c == char16_t('/') || c == char16_t('*') || c == char16_t('\\'));
|
|
}
|
|
|
|
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, char16_t('_'));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketBuffer* aMessage)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aMessage);
|
|
|
|
const uint8_t* data = aMessage->GetData();
|
|
int frameHeaderLength = 0;
|
|
|
|
// See if this is the first part of each Put packet
|
|
if (mPutPacketReceivedLength == 0) {
|
|
// Section 3.3.3 "Put", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
frameHeaderLength = 3;
|
|
|
|
mPacketLength = BigEndian::readUint16(&data[1]) - frameHeaderLength;
|
|
|
|
/**
|
|
* 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 mPutPacketReceivedLength to indicate if
|
|
* current PUT request is done.
|
|
*/
|
|
mReceivedDataBuffer = new uint8_t[mPacketLength];
|
|
mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal);
|
|
}
|
|
|
|
int dataLength = aMessage->GetSize() - frameHeaderLength;
|
|
|
|
// Check length before memcpy to prevent from memory pollution
|
|
if (dataLength < 0 ||
|
|
mPutPacketReceivedLength + dataLength > mPacketLength) {
|
|
BT_LOGR("Received packet size is unreasonable");
|
|
|
|
ReplyToPut(mPutFinalFlag, false);
|
|
DeleteReceivedFile();
|
|
FileTransferComplete();
|
|
|
|
return false;
|
|
}
|
|
|
|
memcpy(mReceivedDataBuffer.get() + mPutPacketReceivedLength,
|
|
&data[frameHeaderLength], dataLength);
|
|
|
|
mPutPacketReceivedLength += dataLength;
|
|
|
|
return (mPutPacketReceivedLength == mPacketLength);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ServerDataHandler(UnixSocketBuffer* aMessage)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
/**
|
|
* Ensure
|
|
* - valid access to data[0], i.e., opCode
|
|
* - received packet length smaller than max packet length
|
|
*/
|
|
int receivedLength = aMessage->GetSize();
|
|
if (receivedLength < 1 || receivedLength > MAX_PACKET_LENGTH) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
uint8_t opCode;
|
|
const uint8_t* data = aMessage->GetData();
|
|
if (mPutPacketReceivedLength > 0) {
|
|
opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
|
|
} else {
|
|
opCode = data[0];
|
|
|
|
// 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;
|
|
if (opCode == ObexRequestCode::Connect) {
|
|
// Section 3.3.1 "Connect", IrOBEX 1.2
|
|
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
|
// [Headers:var]
|
|
if (receivedLength < 7 ||
|
|
!ParseHeaders(&data[7], receivedLength - 7, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
ReplyToConnect();
|
|
AfterOppConnected();
|
|
} else if (opCode == ObexRequestCode::Abort) {
|
|
// Section 3.3.5 "Abort", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
if (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
ReplyToDisconnectOrAbort();
|
|
DeleteReceivedFile();
|
|
} else if (opCode == ObexRequestCode::Disconnect) {
|
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
if (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
ReplyToDisconnectOrAbort();
|
|
AfterOppDisconnected();
|
|
FileTransferComplete();
|
|
} else if (opCode == ObexRequestCode::Put ||
|
|
opCode == ObexRequestCode::PutFinal) {
|
|
if (!ComposePacket(opCode, aMessage)) {
|
|
return;
|
|
}
|
|
|
|
// A Put packet is received completely
|
|
ParseHeaders(mReceivedDataBuffer.get(),
|
|
mPutPacketReceivedLength, &pktHeaders);
|
|
ExtractPacketHeaders(pktHeaders);
|
|
ValidateFileName();
|
|
|
|
// 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;
|
|
RestoreReceivedFileAndNotify();
|
|
FileTransferComplete();
|
|
}
|
|
} 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::ClientDataHandler(UnixSocketBuffer* aMessage)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Ensure valid access to data[0], i.e., opCode
|
|
int receivedLength = aMessage->GetSize();
|
|
if (receivedLength < 1) {
|
|
BT_LOGR("Receive empty response packet");
|
|
return;
|
|
}
|
|
|
|
const uint8_t* data = aMessage->GetData();
|
|
uint8_t opCode = data[0];
|
|
|
|
// 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 == (int) mBatches[0].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();
|
|
|
|
// Ensure valid access to remote information
|
|
if (receivedLength < 7) {
|
|
BT_LOGR("The length of connect response packet is invalid");
|
|
SendDisconnectRequest();
|
|
return;
|
|
}
|
|
|
|
// Keep remote information
|
|
mRemoteObexVersion = data[3];
|
|
mRemoteConnectionFlags = data[4];
|
|
mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]);
|
|
|
|
// The length of file name exceeds maximum length.
|
|
int fileNameByteLen = (mFileName.Length() + 1) * 2;
|
|
int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize;
|
|
if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) {
|
|
BT_WARNING("The length of file name is aberrant.");
|
|
SendDisconnectRequest();
|
|
return;
|
|
}
|
|
|
|
SendPutHeaderRequest(mFileName, mFileLength);
|
|
} else if (mLastCommand == ObexRequestCode::Put) {
|
|
if (mWaitingToSendPutFinal) {
|
|
SendPutFinalRequest();
|
|
return;
|
|
}
|
|
|
|
if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) {
|
|
UpdateProgress();
|
|
mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1;
|
|
}
|
|
|
|
ErrorResult rv;
|
|
if (!mInputStream) {
|
|
mBlob->GetInternalStream(getter_AddRefs(mInputStream), rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
SendDisconnectRequest();
|
|
rv.SuppressException();
|
|
return;
|
|
}
|
|
}
|
|
|
|
RefPtr<ReadFileTask> task = new ReadFileTask(mInputStream,
|
|
mRemoteMaxPacketLength);
|
|
rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
SendDisconnectRequest();
|
|
rv.SuppressException();
|
|
return;
|
|
}
|
|
} else {
|
|
BT_WARNING("Unhandled ObexRequestCode");
|
|
}
|
|
}
|
|
|
|
// Virtual function of class SocketConsumer
|
|
void
|
|
BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
|
nsAutoPtr<UnixSocketBuffer>& aBuffer)
|
|
{
|
|
if (mIsServer) {
|
|
ServerDataHandler(aBuffer);
|
|
} else {
|
|
ClientDataHandler(aBuffer);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int len = aFileName.Length();
|
|
auto fileName = MakeUnique<uint8_t[]>((len + 1) * 2);
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
BigEndian::writeUint16(&fileName[i * 2], aFileName[i]);
|
|
}
|
|
BigEndian::writeUint16(&fileName[len * 2], 0);
|
|
|
|
// Prepare packet
|
|
auto req = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
int index = 3;
|
|
index += AppendHeaderName(&req[index], mRemoteMaxPacketLength - index,
|
|
fileName.get(), (len + 1) * 2);
|
|
index += AppendHeaderLength(&req[index], aFileSize);
|
|
|
|
// This is final put packet if file size equals to 0
|
|
uint8_t opcode = (aFileSize > 0) ? ObexRequestCode::Put
|
|
: ObexRequestCode::PutFinal;
|
|
SendObexData(Move(req), opcode, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendPutRequest(uint8_t* aFileBody,
|
|
int aFileBodyLength)
|
|
{
|
|
if (!mConnected) {
|
|
return;
|
|
}
|
|
|
|
int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize;
|
|
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]
|
|
auto req = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
|
|
int index = 3;
|
|
index += AppendHeaderBody(&req[index], mRemoteMaxPacketLength - index,
|
|
aFileBody, aFileBodyLength);
|
|
|
|
SendObexData(Move(req), ObexRequestCode::Put, index);
|
|
|
|
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;
|
|
auto req = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
index += AppendHeaderEndOfBody(&req[index]);
|
|
|
|
SendObexData(Move(req), ObexRequestCode::PutFinal, index);
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::GetAddress(BluetoothAddress& 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;
|
|
}
|
|
|
|
// The received length can be reset here because this is where we reply to a
|
|
// complete put packet.
|
|
mPutPacketReceivedLength = 0;
|
|
|
|
// 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)
|
|
{
|
|
BT_LOGR("error: %d", aError);
|
|
|
|
// 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)
|
|
{
|
|
if (!mIsServer) {
|
|
mLastCommand = aOpcode;
|
|
}
|
|
|
|
SetObexPacketInfo(aData, aOpcode, aSize);
|
|
mSocket->SendSocketData(new UnixSocketRawData(aData, aSize));
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendObexData(UniquePtr<uint8_t[]> aData, uint8_t aOpcode,
|
|
int aSize)
|
|
{
|
|
if (!mIsServer) {
|
|
mLastCommand = aOpcode;
|
|
}
|
|
|
|
SetObexPacketInfo(aData.get(), aOpcode, aSize);
|
|
mSocket->SendSocketData(new UnixSocketRawData(Move(aData), aSize));
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::FileTransferComplete()
|
|
{
|
|
if (mSendTransferCompleteFlag) {
|
|
return;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-complete");
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
|
|
AppendNamedValue(parameters, "address", mDeviceAddress);
|
|
AppendNamedValue(parameters, "success", mSuccessFlag);
|
|
AppendNamedValue(parameters, "received", mIsServer);
|
|
AppendNamedValue(parameters, "fileName", mFileName);
|
|
AppendNamedValue(parameters, "fileLength", mSentFileLength);
|
|
AppendNamedValue(parameters, "contentType", mContentType);
|
|
|
|
BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
|
|
|
|
mSendTransferCompleteFlag = true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::StartFileTransfer()
|
|
{
|
|
NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-start");
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
|
|
AppendNamedValue(parameters, "address", mDeviceAddress);
|
|
AppendNamedValue(parameters, "received", mIsServer);
|
|
AppendNamedValue(parameters, "fileName", mFileName);
|
|
AppendNamedValue(parameters, "fileLength", mFileLength);
|
|
AppendNamedValue(parameters, "contentType", mContentType);
|
|
|
|
BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
|
|
|
|
mSendTransferCompleteFlag = false;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::UpdateProgress()
|
|
{
|
|
NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-update-progress");
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
|
|
AppendNamedValue(parameters, "address", mDeviceAddress);
|
|
AppendNamedValue(parameters, "received", mIsServer);
|
|
AppendNamedValue(parameters, "processedLength", mSentFileLength);
|
|
AppendNamedValue(parameters, "fileLength", mFileLength);
|
|
|
|
BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReceivingFileConfirmation()
|
|
{
|
|
NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-receiving-file-confirmation");
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
|
|
AppendNamedValue(parameters, "address", mDeviceAddress);
|
|
AppendNamedValue(parameters, "fileName", mFileName);
|
|
AppendNamedValue(parameters, "fileLength", mFileLength);
|
|
AppendNamedValue(parameters, "contentType", mContentType);
|
|
|
|
BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
|
|
}
|
|
|
|
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)
|
|
{
|
|
BT_LOGR("[%s]", (mIsServer)? "server" : "client");
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
/**
|
|
* If the created connection is an inbound connection, close server socket
|
|
* because currently only one file-transfer session is allowed. After that,
|
|
* we need to make sure that server socket would be nulled out.
|
|
* As for outbound connections, we just notify the controller that it's done.
|
|
*/
|
|
if (aSocket == mServerSocket) {
|
|
MOZ_ASSERT(!mSocket);
|
|
mServerSocket.swap(mSocket);
|
|
}
|
|
|
|
// Cache device address since we can't get socket address when a remote
|
|
// device disconnect with us.
|
|
mSocket->GetAddress(mDeviceAddress);
|
|
|
|
// Start sending file if we connect as a client
|
|
if (!mIsServer) {
|
|
StartSendingNextFile();
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket)
|
|
{
|
|
BT_LOGR("[%s]", (mIsServer)? "server" : "client");
|
|
|
|
if (mServerSocket &&
|
|
mServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mServerSocket->Close();
|
|
}
|
|
mServerSocket = nullptr;
|
|
|
|
if (mSocket &&
|
|
mSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
|
|
mSocket->Close();
|
|
}
|
|
mSocket = nullptr;
|
|
|
|
if (!mIsServer) {
|
|
// Inform gaia of remaining blobs' sending failure
|
|
DiscardBlobsToSend();
|
|
}
|
|
|
|
// Listen as a server if there's no more batch to process
|
|
if (!ProcessNextBatch()) {
|
|
Listen();
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
if (aSocket != mSocket) {
|
|
// Do nothing when a listening server socket is closed.
|
|
return;
|
|
}
|
|
BT_LOGR("[%s]", (mIsServer) ? "server" : "client");
|
|
|
|
/**
|
|
* 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) {
|
|
// Remove received file and inform gaia of receiving failure
|
|
DeleteReceivedFile();
|
|
FileTransferComplete();
|
|
} else {
|
|
// Inform gaia of current blob transfer failure
|
|
if (mCurrentBlobIndex >= 0) {
|
|
FileTransferComplete();
|
|
}
|
|
// Inform gaia of remaining blobs' sending failure
|
|
DiscardBlobsToSend();
|
|
}
|
|
}
|
|
|
|
AfterOppDisconnected();
|
|
mDeviceAddress.Clear();
|
|
mSuccessFlag = false;
|
|
|
|
mSocket = nullptr; // should already be closed
|
|
|
|
// Listen as a server if there's no more batch to process
|
|
if (!ProcessNextBatch()) {
|
|
Listen();
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
|
|
{
|
|
if (mSocket) {
|
|
mSocket->Close();
|
|
} else {
|
|
BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(BluetoothOppManager, nsIObserver)
|
|
|
|
bool
|
|
BluetoothOppManager::AcquireSdcardMountLock()
|
|
{
|
|
nsCOMPtr<nsIVolumeService> volumeSrv =
|
|
do_GetService(NS_VOLUMESERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(volumeSrv, false);
|
|
|
|
NS_ENSURE_SUCCESS(
|
|
volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"),
|
|
getter_AddRefs(mMountLock)),
|
|
false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnGetServiceChannel(
|
|
const BluetoothAddress& aDeviceAddress,
|
|
const BluetoothUuid& aServiceUuid,
|
|
int aChannel)
|
|
{
|
|
if (aChannel < 0) {
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
if (!bs || sInShutdown) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
|
|
if (mNeedsUpdatingSdpRecords) {
|
|
mNeedsUpdatingSdpRecords = false;
|
|
mLastServiceChannelCheck = TimeStamp::Now();
|
|
} else {
|
|
// Refresh SDP records until it gets valid service channel
|
|
// unless timeout is hit.
|
|
TimeDuration duration = TimeStamp::Now() - mLastServiceChannelCheck;
|
|
if (duration.ToMilliseconds() >= kSdpUpdatingTimeoutMs) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!bs->UpdateSdpRecords(aDeviceAddress, this)) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
|
|
return; // We update the service records before we connect.
|
|
}
|
|
|
|
mSocket->Connect(aDeviceAddress, aServiceUuid,
|
|
BluetoothSocketType::RFCOMM, aChannel,
|
|
false, true);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnUpdateSdpRecords(const BluetoothAddress& aDeviceAddress)
|
|
{
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
if (!bs || sInShutdown) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
|
|
if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, kObexObjectPush, this))) {
|
|
OnSocketConnectError(mSocket);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::Connect(const BluetoothAddress& aDeviceAddress,
|
|
BluetoothProfileController* aController)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnConnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::Reset()
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
END_BLUETOOTH_NAMESPACE
|