Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ |
michael@0 | 2 | /* vim: set ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 5 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "base/basictypes.h" |
michael@0 | 8 | #include "BluetoothOppManager.h" |
michael@0 | 9 | |
michael@0 | 10 | #include "BluetoothService.h" |
michael@0 | 11 | #include "BluetoothSocket.h" |
michael@0 | 12 | #include "BluetoothUtils.h" |
michael@0 | 13 | #include "BluetoothUuid.h" |
michael@0 | 14 | #include "ObexBase.h" |
michael@0 | 15 | |
michael@0 | 16 | #include "mozilla/dom/bluetooth/BluetoothTypes.h" |
michael@0 | 17 | #include "mozilla/RefPtr.h" |
michael@0 | 18 | #include "mozilla/Services.h" |
michael@0 | 19 | #include "mozilla/StaticPtr.h" |
michael@0 | 20 | #include "nsAutoPtr.h" |
michael@0 | 21 | #include "nsCExternalHandlerService.h" |
michael@0 | 22 | #include "nsIObserver.h" |
michael@0 | 23 | #include "nsIObserverService.h" |
michael@0 | 24 | #include "nsIDOMFile.h" |
michael@0 | 25 | #include "nsIFile.h" |
michael@0 | 26 | #include "nsIInputStream.h" |
michael@0 | 27 | #include "nsIMIMEService.h" |
michael@0 | 28 | #include "nsIOutputStream.h" |
michael@0 | 29 | #include "nsIVolumeService.h" |
michael@0 | 30 | #include "nsNetUtil.h" |
michael@0 | 31 | #include "nsServiceManagerUtils.h" |
michael@0 | 32 | |
michael@0 | 33 | #define TARGET_SUBDIR "Download/Bluetooth/" |
michael@0 | 34 | |
michael@0 | 35 | USING_BLUETOOTH_NAMESPACE |
michael@0 | 36 | using namespace mozilla; |
michael@0 | 37 | using namespace mozilla::ipc; |
michael@0 | 38 | |
michael@0 | 39 | namespace { |
michael@0 | 40 | // Sending system message "bluetooth-opp-update-progress" every 50kb |
michael@0 | 41 | static const uint32_t kUpdateProgressBase = 50 * 1024; |
michael@0 | 42 | |
michael@0 | 43 | /* |
michael@0 | 44 | * The format of the header of an PUT request is |
michael@0 | 45 | * [opcode:1][packet length:2][headerId:1][header length:2] |
michael@0 | 46 | */ |
michael@0 | 47 | static const uint32_t kPutRequestHeaderSize = 6; |
michael@0 | 48 | |
michael@0 | 49 | /* |
michael@0 | 50 | * The format of the appended header of an PUT request is |
michael@0 | 51 | * [headerId:1][header length:4] |
michael@0 | 52 | * P.S. Length of name header is 4 since unicode is 2 bytes per char. |
michael@0 | 53 | */ |
michael@0 | 54 | static const uint32_t kPutRequestAppendHeaderSize = 5; |
michael@0 | 55 | |
michael@0 | 56 | StaticRefPtr<BluetoothOppManager> sBluetoothOppManager; |
michael@0 | 57 | static bool sInShutdown = false; |
michael@0 | 58 | } |
michael@0 | 59 | |
michael@0 | 60 | class mozilla::dom::bluetooth::SendFileBatch { |
michael@0 | 61 | public: |
michael@0 | 62 | SendFileBatch(const nsAString& aDeviceAddress, nsIDOMBlob* aBlob) |
michael@0 | 63 | : mDeviceAddress(aDeviceAddress) |
michael@0 | 64 | { |
michael@0 | 65 | mBlobs.AppendElement(aBlob); |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | nsString mDeviceAddress; |
michael@0 | 69 | nsCOMArray<nsIDOMBlob> mBlobs; |
michael@0 | 70 | }; |
michael@0 | 71 | |
michael@0 | 72 | NS_IMETHODIMP |
michael@0 | 73 | BluetoothOppManager::Observe(nsISupports* aSubject, |
michael@0 | 74 | const char* aTopic, |
michael@0 | 75 | const char16_t* aData) |
michael@0 | 76 | { |
michael@0 | 77 | MOZ_ASSERT(sBluetoothOppManager); |
michael@0 | 78 | |
michael@0 | 79 | if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { |
michael@0 | 80 | HandleShutdown(); |
michael@0 | 81 | return NS_OK; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!"); |
michael@0 | 85 | return NS_ERROR_UNEXPECTED; |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | class SendSocketDataTask : public nsRunnable |
michael@0 | 89 | { |
michael@0 | 90 | public: |
michael@0 | 91 | SendSocketDataTask(uint8_t* aStream, uint32_t aSize) |
michael@0 | 92 | : mStream(aStream) |
michael@0 | 93 | , mSize(aSize) |
michael@0 | 94 | { |
michael@0 | 95 | MOZ_ASSERT(!NS_IsMainThread()); |
michael@0 | 96 | } |
michael@0 | 97 | |
michael@0 | 98 | NS_IMETHOD Run() |
michael@0 | 99 | { |
michael@0 | 100 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 101 | |
michael@0 | 102 | sBluetoothOppManager->SendPutRequest(mStream, mSize); |
michael@0 | 103 | |
michael@0 | 104 | return NS_OK; |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | private: |
michael@0 | 108 | nsAutoArrayPtr<uint8_t> mStream; |
michael@0 | 109 | uint32_t mSize; |
michael@0 | 110 | }; |
michael@0 | 111 | |
michael@0 | 112 | class ReadFileTask : public nsRunnable |
michael@0 | 113 | { |
michael@0 | 114 | public: |
michael@0 | 115 | ReadFileTask(nsIInputStream* aInputStream, |
michael@0 | 116 | uint32_t aRemoteMaxPacketSize) : mInputStream(aInputStream) |
michael@0 | 117 | { |
michael@0 | 118 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 119 | |
michael@0 | 120 | mAvailablePacketSize = aRemoteMaxPacketSize - kPutRequestHeaderSize; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | NS_IMETHOD Run() |
michael@0 | 124 | { |
michael@0 | 125 | MOZ_ASSERT(!NS_IsMainThread()); |
michael@0 | 126 | |
michael@0 | 127 | uint32_t numRead; |
michael@0 | 128 | nsAutoArrayPtr<char> buf(new char[mAvailablePacketSize]); |
michael@0 | 129 | |
michael@0 | 130 | // function inputstream->Read() only works on non-main thread |
michael@0 | 131 | nsresult rv = mInputStream->Read(buf, mAvailablePacketSize, &numRead); |
michael@0 | 132 | if (NS_FAILED(rv)) { |
michael@0 | 133 | // Needs error handling here |
michael@0 | 134 | BT_WARNING("Failed to read from input stream"); |
michael@0 | 135 | return NS_ERROR_FAILURE; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | if (numRead > 0) { |
michael@0 | 139 | sBluetoothOppManager->CheckPutFinal(numRead); |
michael@0 | 140 | |
michael@0 | 141 | nsRefPtr<SendSocketDataTask> task = |
michael@0 | 142 | new SendSocketDataTask((uint8_t*)buf.forget(), numRead); |
michael@0 | 143 | if (NS_FAILED(NS_DispatchToMainThread(task))) { |
michael@0 | 144 | BT_WARNING("Failed to dispatch to main thread!"); |
michael@0 | 145 | return NS_ERROR_FAILURE; |
michael@0 | 146 | } |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | return NS_OK; |
michael@0 | 150 | }; |
michael@0 | 151 | |
michael@0 | 152 | private: |
michael@0 | 153 | nsCOMPtr<nsIInputStream> mInputStream; |
michael@0 | 154 | uint32_t mAvailablePacketSize; |
michael@0 | 155 | }; |
michael@0 | 156 | |
michael@0 | 157 | class CloseSocketTask : public Task |
michael@0 | 158 | { |
michael@0 | 159 | public: |
michael@0 | 160 | CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket) |
michael@0 | 161 | { |
michael@0 | 162 | MOZ_ASSERT(aSocket); |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | void Run() MOZ_OVERRIDE |
michael@0 | 166 | { |
michael@0 | 167 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 168 | mSocket->CloseDroidSocket(); |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | private: |
michael@0 | 172 | nsRefPtr<BluetoothSocket> mSocket; |
michael@0 | 173 | }; |
michael@0 | 174 | |
michael@0 | 175 | BluetoothOppManager::BluetoothOppManager() : mConnected(false) |
michael@0 | 176 | , mRemoteObexVersion(0) |
michael@0 | 177 | , mRemoteConnectionFlags(0) |
michael@0 | 178 | , mRemoteMaxPacketLength(0) |
michael@0 | 179 | , mLastCommand(0) |
michael@0 | 180 | , mPacketLength(0) |
michael@0 | 181 | , mPutPacketReceivedLength(0) |
michael@0 | 182 | , mBodySegmentLength(0) |
michael@0 | 183 | , mAbortFlag(false) |
michael@0 | 184 | , mNewFileFlag(false) |
michael@0 | 185 | , mPutFinalFlag(false) |
michael@0 | 186 | , mSendTransferCompleteFlag(false) |
michael@0 | 187 | , mSuccessFlag(false) |
michael@0 | 188 | , mIsServer(true) |
michael@0 | 189 | , mWaitingForConfirmationFlag(false) |
michael@0 | 190 | , mFileLength(0) |
michael@0 | 191 | , mSentFileLength(0) |
michael@0 | 192 | , mWaitingToSendPutFinal(false) |
michael@0 | 193 | , mCurrentBlobIndex(-1) |
michael@0 | 194 | { |
michael@0 | 195 | mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | BluetoothOppManager::~BluetoothOppManager() |
michael@0 | 199 | { |
michael@0 | 200 | nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
michael@0 | 201 | NS_ENSURE_TRUE_VOID(obs); |
michael@0 | 202 | if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { |
michael@0 | 203 | BT_WARNING("Failed to remove shutdown observer!"); |
michael@0 | 204 | } |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | bool |
michael@0 | 208 | BluetoothOppManager::Init() |
michael@0 | 209 | { |
michael@0 | 210 | nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
michael@0 | 211 | NS_ENSURE_TRUE(obs, false); |
michael@0 | 212 | if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { |
michael@0 | 213 | BT_WARNING("Failed to add shutdown observer!"); |
michael@0 | 214 | return false; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | /** |
michael@0 | 218 | * We don't start listening here as BluetoothServiceBluedroid calls Listen() |
michael@0 | 219 | * immediately when BT stops. |
michael@0 | 220 | * |
michael@0 | 221 | * If we start listening here, the listening fails when device boots up since |
michael@0 | 222 | * Listen() is called again and restarts server socket. The restart causes |
michael@0 | 223 | * absence of read events when device boots up. |
michael@0 | 224 | */ |
michael@0 | 225 | |
michael@0 | 226 | return true; |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | //static |
michael@0 | 230 | BluetoothOppManager* |
michael@0 | 231 | BluetoothOppManager::Get() |
michael@0 | 232 | { |
michael@0 | 233 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 234 | |
michael@0 | 235 | // If sBluetoothOppManager already exists, exit early |
michael@0 | 236 | if (sBluetoothOppManager) { |
michael@0 | 237 | return sBluetoothOppManager; |
michael@0 | 238 | } |
michael@0 | 239 | |
michael@0 | 240 | // If we're in shutdown, don't create a new instance |
michael@0 | 241 | NS_ENSURE_FALSE(sInShutdown, nullptr); |
michael@0 | 242 | |
michael@0 | 243 | // Create a new instance, register, and return |
michael@0 | 244 | BluetoothOppManager *manager = new BluetoothOppManager(); |
michael@0 | 245 | NS_ENSURE_TRUE(manager->Init(), nullptr); |
michael@0 | 246 | |
michael@0 | 247 | sBluetoothOppManager = manager; |
michael@0 | 248 | return sBluetoothOppManager; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | void |
michael@0 | 252 | BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress) |
michael@0 | 253 | { |
michael@0 | 254 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 255 | |
michael@0 | 256 | // Stop listening because currently we only support one connection at a time. |
michael@0 | 257 | if (mServerSocket) { |
michael@0 | 258 | mServerSocket->Disconnect(); |
michael@0 | 259 | mServerSocket = nullptr; |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | mIsServer = false; |
michael@0 | 263 | |
michael@0 | 264 | BluetoothService* bs = BluetoothService::Get(); |
michael@0 | 265 | if (!bs || sInShutdown || mSocket) { |
michael@0 | 266 | OnSocketConnectError(mSocket); |
michael@0 | 267 | return; |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | mSocket = |
michael@0 | 271 | new BluetoothSocket(this, BluetoothSocketType::RFCOMM, false, true); |
michael@0 | 272 | mSocket->Connect(aDeviceAddress, -1); |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | void |
michael@0 | 276 | BluetoothOppManager::HandleShutdown() |
michael@0 | 277 | { |
michael@0 | 278 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 279 | sInShutdown = true; |
michael@0 | 280 | Disconnect(nullptr); |
michael@0 | 281 | sBluetoothOppManager = nullptr; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | bool |
michael@0 | 285 | BluetoothOppManager::Listen() |
michael@0 | 286 | { |
michael@0 | 287 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 288 | |
michael@0 | 289 | if (mSocket) { |
michael@0 | 290 | BT_WARNING("mSocket exists. Failed to listen."); |
michael@0 | 291 | return false; |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | /** |
michael@0 | 295 | * Restart server socket since its underlying fd becomes invalid when |
michael@0 | 296 | * BT stops; otherwise no more read events would be received even if |
michael@0 | 297 | * BT restarts. |
michael@0 | 298 | */ |
michael@0 | 299 | if (mServerSocket) { |
michael@0 | 300 | mServerSocket->Disconnect(); |
michael@0 | 301 | mServerSocket = nullptr; |
michael@0 | 302 | } |
michael@0 | 303 | |
michael@0 | 304 | mServerSocket = |
michael@0 | 305 | new BluetoothSocket(this, BluetoothSocketType::RFCOMM, false, true); |
michael@0 | 306 | |
michael@0 | 307 | if (!mServerSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) { |
michael@0 | 308 | BT_WARNING("[OPP] Can't listen on RFCOMM socket!"); |
michael@0 | 309 | mServerSocket = nullptr; |
michael@0 | 310 | return false; |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | mIsServer = true; |
michael@0 | 314 | |
michael@0 | 315 | return true; |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | void |
michael@0 | 319 | BluetoothOppManager::StartSendingNextFile() |
michael@0 | 320 | { |
michael@0 | 321 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 322 | |
michael@0 | 323 | MOZ_ASSERT(!IsConnected()); |
michael@0 | 324 | MOZ_ASSERT(!mBatches.IsEmpty()); |
michael@0 | 325 | MOZ_ASSERT((int)mBatches[0].mBlobs.Length() > mCurrentBlobIndex + 1); |
michael@0 | 326 | |
michael@0 | 327 | mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; |
michael@0 | 328 | |
michael@0 | 329 | // Before sending content, we have to send a header including |
michael@0 | 330 | // information such as file name, file length and content type. |
michael@0 | 331 | ExtractBlobHeaders(); |
michael@0 | 332 | StartFileTransfer(); |
michael@0 | 333 | |
michael@0 | 334 | if (mCurrentBlobIndex == 0) { |
michael@0 | 335 | // We may have more than one file waiting for transferring, but only one |
michael@0 | 336 | // CONNECT request would be sent. Therefore check if this is the very first |
michael@0 | 337 | // file at the head of queue. |
michael@0 | 338 | SendConnectRequest(); |
michael@0 | 339 | } else { |
michael@0 | 340 | SendPutHeaderRequest(mFileName, mFileLength); |
michael@0 | 341 | AfterFirstPut(); |
michael@0 | 342 | } |
michael@0 | 343 | } |
michael@0 | 344 | |
michael@0 | 345 | bool |
michael@0 | 346 | BluetoothOppManager::SendFile(const nsAString& aDeviceAddress, |
michael@0 | 347 | BlobParent* aActor) |
michael@0 | 348 | { |
michael@0 | 349 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 350 | |
michael@0 | 351 | nsCOMPtr<nsIDOMBlob> blob = aActor->GetBlob(); |
michael@0 | 352 | |
michael@0 | 353 | return SendFile(aDeviceAddress, blob.get()); |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | bool |
michael@0 | 357 | BluetoothOppManager::SendFile(const nsAString& aDeviceAddress, |
michael@0 | 358 | nsIDOMBlob* aBlob) |
michael@0 | 359 | { |
michael@0 | 360 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 361 | |
michael@0 | 362 | AppendBlobToSend(aDeviceAddress, aBlob); |
michael@0 | 363 | if (!mSocket) { |
michael@0 | 364 | ProcessNextBatch(); |
michael@0 | 365 | } |
michael@0 | 366 | |
michael@0 | 367 | return true; |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | void |
michael@0 | 371 | BluetoothOppManager::AppendBlobToSend(const nsAString& aDeviceAddress, |
michael@0 | 372 | nsIDOMBlob* aBlob) |
michael@0 | 373 | { |
michael@0 | 374 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 375 | |
michael@0 | 376 | int indexTail = mBatches.Length() - 1; |
michael@0 | 377 | |
michael@0 | 378 | /** |
michael@0 | 379 | * Create a new batch if |
michael@0 | 380 | * - mBatches is empty, or |
michael@0 | 381 | * - aDeviceAddress differs from mDeviceAddress of the last batch |
michael@0 | 382 | */ |
michael@0 | 383 | if (mBatches.IsEmpty() || |
michael@0 | 384 | aDeviceAddress != mBatches[indexTail].mDeviceAddress) { |
michael@0 | 385 | SendFileBatch batch(aDeviceAddress, aBlob); |
michael@0 | 386 | mBatches.AppendElement(batch); |
michael@0 | 387 | } else { |
michael@0 | 388 | mBatches[indexTail].mBlobs.AppendElement(aBlob); |
michael@0 | 389 | } |
michael@0 | 390 | } |
michael@0 | 391 | |
michael@0 | 392 | void |
michael@0 | 393 | BluetoothOppManager::DiscardBlobsToSend() |
michael@0 | 394 | { |
michael@0 | 395 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 396 | |
michael@0 | 397 | MOZ_ASSERT(!mBatches.IsEmpty()); |
michael@0 | 398 | MOZ_ASSERT(!mIsServer); |
michael@0 | 399 | |
michael@0 | 400 | int length = (int) mBatches[0].mBlobs.Length(); |
michael@0 | 401 | while (length > mCurrentBlobIndex + 1) { |
michael@0 | 402 | mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; |
michael@0 | 403 | |
michael@0 | 404 | BT_LOGR("idx %d", mCurrentBlobIndex); |
michael@0 | 405 | ExtractBlobHeaders(); |
michael@0 | 406 | StartFileTransfer(); |
michael@0 | 407 | FileTransferComplete(); |
michael@0 | 408 | } |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | bool |
michael@0 | 412 | BluetoothOppManager::ProcessNextBatch() |
michael@0 | 413 | { |
michael@0 | 414 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 415 | |
michael@0 | 416 | // Remove the processed batch. |
michael@0 | 417 | // A batch is processed if we've incremented mCurrentBlobIndex for it. |
michael@0 | 418 | if (mCurrentBlobIndex >= 0) { |
michael@0 | 419 | ClearQueue(); |
michael@0 | 420 | mBatches.RemoveElementAt(0); |
michael@0 | 421 | BT_LOGR("REMOVE. %d remaining", mBatches.Length()); |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | // Process the next batch |
michael@0 | 425 | if (!mBatches.IsEmpty()) { |
michael@0 | 426 | ConnectInternal(mBatches[0].mDeviceAddress); |
michael@0 | 427 | return true; |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | // No more batch to process |
michael@0 | 431 | return false; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | void |
michael@0 | 435 | BluetoothOppManager::ClearQueue() |
michael@0 | 436 | { |
michael@0 | 437 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 438 | |
michael@0 | 439 | MOZ_ASSERT(!mIsServer); |
michael@0 | 440 | MOZ_ASSERT(!mBatches.IsEmpty()); |
michael@0 | 441 | MOZ_ASSERT(!mBatches[0].mBlobs.IsEmpty()); |
michael@0 | 442 | |
michael@0 | 443 | mCurrentBlobIndex = -1; |
michael@0 | 444 | mBlob = nullptr; |
michael@0 | 445 | mBatches[0].mBlobs.Clear(); |
michael@0 | 446 | } |
michael@0 | 447 | |
michael@0 | 448 | bool |
michael@0 | 449 | BluetoothOppManager::StopSendingFile() |
michael@0 | 450 | { |
michael@0 | 451 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 452 | |
michael@0 | 453 | if (mIsServer) { |
michael@0 | 454 | mAbortFlag = true; |
michael@0 | 455 | } else { |
michael@0 | 456 | Disconnect(nullptr); |
michael@0 | 457 | } |
michael@0 | 458 | |
michael@0 | 459 | return true; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | bool |
michael@0 | 463 | BluetoothOppManager::ConfirmReceivingFile(bool aConfirm) |
michael@0 | 464 | { |
michael@0 | 465 | NS_ENSURE_TRUE(mConnected, false); |
michael@0 | 466 | NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false); |
michael@0 | 467 | |
michael@0 | 468 | mWaitingForConfirmationFlag = false; |
michael@0 | 469 | |
michael@0 | 470 | // For the first packet of first file |
michael@0 | 471 | bool success = false; |
michael@0 | 472 | if (aConfirm) { |
michael@0 | 473 | StartFileTransfer(); |
michael@0 | 474 | if (CreateFile()) { |
michael@0 | 475 | success = WriteToFile(mBodySegment.get(), mBodySegmentLength); |
michael@0 | 476 | } |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | if (success && mPutFinalFlag) { |
michael@0 | 480 | mSuccessFlag = true; |
michael@0 | 481 | FileTransferComplete(); |
michael@0 | 482 | NotifyAboutFileChange(); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | ReplyToPut(mPutFinalFlag, success); |
michael@0 | 486 | |
michael@0 | 487 | return true; |
michael@0 | 488 | } |
michael@0 | 489 | |
michael@0 | 490 | void |
michael@0 | 491 | BluetoothOppManager::AfterFirstPut() |
michael@0 | 492 | { |
michael@0 | 493 | mUpdateProgressCounter = 1; |
michael@0 | 494 | mPutFinalFlag = false; |
michael@0 | 495 | mPutPacketReceivedLength = 0; |
michael@0 | 496 | mSentFileLength = 0; |
michael@0 | 497 | mWaitingToSendPutFinal = false; |
michael@0 | 498 | mSuccessFlag = false; |
michael@0 | 499 | mBodySegmentLength = 0; |
michael@0 | 500 | } |
michael@0 | 501 | |
michael@0 | 502 | void |
michael@0 | 503 | BluetoothOppManager::AfterOppConnected() |
michael@0 | 504 | { |
michael@0 | 505 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 506 | |
michael@0 | 507 | mConnected = true; |
michael@0 | 508 | mAbortFlag = false; |
michael@0 | 509 | mWaitingForConfirmationFlag = true; |
michael@0 | 510 | AfterFirstPut(); |
michael@0 | 511 | // Get a mount lock to prevent the sdcard from being shared with |
michael@0 | 512 | // the PC while we're doing a OPP file transfer. After OPP transaction |
michael@0 | 513 | // were done, the mount lock will be freed. |
michael@0 | 514 | if (!AcquireSdcardMountLock()) { |
michael@0 | 515 | // If we fail to get a mount lock, abort this transaction |
michael@0 | 516 | // Directly sending disconnect-request is better than abort-request |
michael@0 | 517 | BT_WARNING("BluetoothOPPManager couldn't get a mount lock!"); |
michael@0 | 518 | Disconnect(nullptr); |
michael@0 | 519 | } |
michael@0 | 520 | } |
michael@0 | 521 | |
michael@0 | 522 | void |
michael@0 | 523 | BluetoothOppManager::AfterOppDisconnected() |
michael@0 | 524 | { |
michael@0 | 525 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 526 | |
michael@0 | 527 | mConnected = false; |
michael@0 | 528 | mLastCommand = 0; |
michael@0 | 529 | mPutPacketReceivedLength = 0; |
michael@0 | 530 | mDsFile = nullptr; |
michael@0 | 531 | |
michael@0 | 532 | // We can't reset mSuccessFlag here since this function may be called |
michael@0 | 533 | // before we send system message of transfer complete |
michael@0 | 534 | // mSuccessFlag = false; |
michael@0 | 535 | |
michael@0 | 536 | if (mInputStream) { |
michael@0 | 537 | mInputStream->Close(); |
michael@0 | 538 | mInputStream = nullptr; |
michael@0 | 539 | } |
michael@0 | 540 | |
michael@0 | 541 | if (mOutputStream) { |
michael@0 | 542 | mOutputStream->Close(); |
michael@0 | 543 | mOutputStream = nullptr; |
michael@0 | 544 | } |
michael@0 | 545 | |
michael@0 | 546 | if (mReadFileThread) { |
michael@0 | 547 | mReadFileThread->Shutdown(); |
michael@0 | 548 | mReadFileThread = nullptr; |
michael@0 | 549 | } |
michael@0 | 550 | // Release the Mount lock if file transfer completed |
michael@0 | 551 | if (mMountLock) { |
michael@0 | 552 | // The mount lock will be implicitly unlocked |
michael@0 | 553 | mMountLock = nullptr; |
michael@0 | 554 | } |
michael@0 | 555 | } |
michael@0 | 556 | |
michael@0 | 557 | void |
michael@0 | 558 | BluetoothOppManager::DeleteReceivedFile() |
michael@0 | 559 | { |
michael@0 | 560 | if (mOutputStream) { |
michael@0 | 561 | mOutputStream->Close(); |
michael@0 | 562 | mOutputStream = nullptr; |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | if (mDsFile && mDsFile->mFile) { |
michael@0 | 566 | mDsFile->mFile->Remove(false); |
michael@0 | 567 | mDsFile = nullptr; |
michael@0 | 568 | } |
michael@0 | 569 | } |
michael@0 | 570 | |
michael@0 | 571 | bool |
michael@0 | 572 | BluetoothOppManager::CreateFile() |
michael@0 | 573 | { |
michael@0 | 574 | MOZ_ASSERT(mPutPacketReceivedLength == mPacketLength); |
michael@0 | 575 | |
michael@0 | 576 | nsString path; |
michael@0 | 577 | path.AssignLiteral(TARGET_SUBDIR); |
michael@0 | 578 | path.Append(mFileName); |
michael@0 | 579 | |
michael@0 | 580 | mDsFile = DeviceStorageFile::CreateUnique( |
michael@0 | 581 | path, nsIFile::NORMAL_FILE_TYPE, 0644); |
michael@0 | 582 | NS_ENSURE_TRUE(mDsFile, false); |
michael@0 | 583 | |
michael@0 | 584 | nsCOMPtr<nsIFile> f; |
michael@0 | 585 | mDsFile->mFile->Clone(getter_AddRefs(f)); |
michael@0 | 586 | |
michael@0 | 587 | /* |
michael@0 | 588 | * The function CreateUnique() may create a file with a different file |
michael@0 | 589 | * name from the original mFileName. Therefore we have to retrieve |
michael@0 | 590 | * the file name again. |
michael@0 | 591 | */ |
michael@0 | 592 | f->GetLeafName(mFileName); |
michael@0 | 593 | |
michael@0 | 594 | NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f); |
michael@0 | 595 | NS_ENSURE_TRUE(mOutputStream, false); |
michael@0 | 596 | |
michael@0 | 597 | return true; |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | bool |
michael@0 | 601 | BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength) |
michael@0 | 602 | { |
michael@0 | 603 | NS_ENSURE_TRUE(mOutputStream, false); |
michael@0 | 604 | |
michael@0 | 605 | uint32_t wrote = 0; |
michael@0 | 606 | mOutputStream->Write((const char*)aData, aDataLength, &wrote); |
michael@0 | 607 | NS_ENSURE_TRUE(aDataLength == (int) wrote, false); |
michael@0 | 608 | |
michael@0 | 609 | return true; |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | // Virtual function of class SocketConsumer |
michael@0 | 613 | void |
michael@0 | 614 | BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader) |
michael@0 | 615 | { |
michael@0 | 616 | if (aHeader.Has(ObexHeaderId::Name)) { |
michael@0 | 617 | aHeader.GetName(mFileName); |
michael@0 | 618 | } |
michael@0 | 619 | |
michael@0 | 620 | if (aHeader.Has(ObexHeaderId::Type)) { |
michael@0 | 621 | aHeader.GetContentType(mContentType); |
michael@0 | 622 | } |
michael@0 | 623 | |
michael@0 | 624 | if (aHeader.Has(ObexHeaderId::Length)) { |
michael@0 | 625 | aHeader.GetLength(&mFileLength); |
michael@0 | 626 | } |
michael@0 | 627 | |
michael@0 | 628 | if (aHeader.Has(ObexHeaderId::Body) || |
michael@0 | 629 | aHeader.Has(ObexHeaderId::EndOfBody)) { |
michael@0 | 630 | uint8_t* bodyPtr; |
michael@0 | 631 | aHeader.GetBody(&bodyPtr); |
michael@0 | 632 | mBodySegment = bodyPtr; |
michael@0 | 633 | |
michael@0 | 634 | aHeader.GetBodyLength(&mBodySegmentLength); |
michael@0 | 635 | } |
michael@0 | 636 | } |
michael@0 | 637 | |
michael@0 | 638 | bool |
michael@0 | 639 | BluetoothOppManager::ExtractBlobHeaders() |
michael@0 | 640 | { |
michael@0 | 641 | RetrieveSentFileName(); |
michael@0 | 642 | |
michael@0 | 643 | nsresult rv = mBlob->GetType(mContentType); |
michael@0 | 644 | if (NS_FAILED(rv)) { |
michael@0 | 645 | BT_WARNING("Can't get content type"); |
michael@0 | 646 | SendDisconnectRequest(); |
michael@0 | 647 | return false; |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | uint64_t fileLength; |
michael@0 | 651 | rv = mBlob->GetSize(&fileLength); |
michael@0 | 652 | if (NS_FAILED(rv)) { |
michael@0 | 653 | BT_WARNING("Can't get file size"); |
michael@0 | 654 | SendDisconnectRequest(); |
michael@0 | 655 | return false; |
michael@0 | 656 | } |
michael@0 | 657 | |
michael@0 | 658 | // Currently we keep the size of files which were sent/received via |
michael@0 | 659 | // Bluetooth not exceed UINT32_MAX because the Length header in OBEX |
michael@0 | 660 | // is only 4-byte long. Although it is possible to transfer a file |
michael@0 | 661 | // larger than UINT32_MAX, it needs to parse another OBEX Header |
michael@0 | 662 | // and I would like to leave it as a feature. |
michael@0 | 663 | if (fileLength > (uint64_t)UINT32_MAX) { |
michael@0 | 664 | BT_WARNING("The file size is too large for now"); |
michael@0 | 665 | SendDisconnectRequest(); |
michael@0 | 666 | return false; |
michael@0 | 667 | } |
michael@0 | 668 | |
michael@0 | 669 | mFileLength = fileLength; |
michael@0 | 670 | rv = NS_NewThread(getter_AddRefs(mReadFileThread)); |
michael@0 | 671 | if (NS_FAILED(rv)) { |
michael@0 | 672 | BT_WARNING("Can't create thread"); |
michael@0 | 673 | SendDisconnectRequest(); |
michael@0 | 674 | return false; |
michael@0 | 675 | } |
michael@0 | 676 | |
michael@0 | 677 | return true; |
michael@0 | 678 | } |
michael@0 | 679 | |
michael@0 | 680 | void |
michael@0 | 681 | BluetoothOppManager::RetrieveSentFileName() |
michael@0 | 682 | { |
michael@0 | 683 | mFileName.Truncate(); |
michael@0 | 684 | |
michael@0 | 685 | nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob); |
michael@0 | 686 | if (file) { |
michael@0 | 687 | file->GetName(mFileName); |
michael@0 | 688 | } |
michael@0 | 689 | |
michael@0 | 690 | /** |
michael@0 | 691 | * We try our best to get the file extention to avoid interoperability issues. |
michael@0 | 692 | * However, once we found that we are unable to get suitable extension or |
michael@0 | 693 | * information about the content type, sending a pre-defined file name without |
michael@0 | 694 | * extension would be fine. |
michael@0 | 695 | */ |
michael@0 | 696 | if (mFileName.IsEmpty()) { |
michael@0 | 697 | mFileName.AssignLiteral("Unknown"); |
michael@0 | 698 | } |
michael@0 | 699 | |
michael@0 | 700 | int32_t offset = mFileName.RFindChar('/'); |
michael@0 | 701 | if (offset != kNotFound) { |
michael@0 | 702 | mFileName = Substring(mFileName, offset + 1); |
michael@0 | 703 | } |
michael@0 | 704 | |
michael@0 | 705 | offset = mFileName.RFindChar('.'); |
michael@0 | 706 | if (offset == kNotFound) { |
michael@0 | 707 | nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID); |
michael@0 | 708 | |
michael@0 | 709 | if (mimeSvc) { |
michael@0 | 710 | nsString mimeType; |
michael@0 | 711 | mBlob->GetType(mimeType); |
michael@0 | 712 | |
michael@0 | 713 | nsCString extension; |
michael@0 | 714 | nsresult rv = |
michael@0 | 715 | mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), |
michael@0 | 716 | EmptyCString(), |
michael@0 | 717 | extension); |
michael@0 | 718 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 719 | mFileName.AppendLiteral("."); |
michael@0 | 720 | AppendUTF8toUTF16(extension, mFileName); |
michael@0 | 721 | } |
michael@0 | 722 | } |
michael@0 | 723 | } |
michael@0 | 724 | } |
michael@0 | 725 | |
michael@0 | 726 | bool |
michael@0 | 727 | BluetoothOppManager::IsReservedChar(char16_t c) |
michael@0 | 728 | { |
michael@0 | 729 | return (c < 0x0020 || |
michael@0 | 730 | c == char16_t('?') || c == char16_t('|') || c == char16_t('<') || |
michael@0 | 731 | c == char16_t('>') || c == char16_t('"') || c == char16_t(':') || |
michael@0 | 732 | c == char16_t('/') || c == char16_t('*') || c == char16_t('\\')); |
michael@0 | 733 | } |
michael@0 | 734 | |
michael@0 | 735 | void |
michael@0 | 736 | BluetoothOppManager::ValidateFileName() |
michael@0 | 737 | { |
michael@0 | 738 | int length = mFileName.Length(); |
michael@0 | 739 | |
michael@0 | 740 | for (int i = 0; i < length; ++i) { |
michael@0 | 741 | // Replace reserved char of fat file system with '_' |
michael@0 | 742 | if (IsReservedChar(mFileName.CharAt(i))) { |
michael@0 | 743 | mFileName.Replace(i, 1, char16_t('_')); |
michael@0 | 744 | } |
michael@0 | 745 | } |
michael@0 | 746 | } |
michael@0 | 747 | |
michael@0 | 748 | bool |
michael@0 | 749 | BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage) |
michael@0 | 750 | { |
michael@0 | 751 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 752 | MOZ_ASSERT(aMessage); |
michael@0 | 753 | |
michael@0 | 754 | int frameHeaderLength = 0; |
michael@0 | 755 | |
michael@0 | 756 | // See if this is the first part of each Put packet |
michael@0 | 757 | if (mPutPacketReceivedLength == 0) { |
michael@0 | 758 | // Section 3.3.3 "Put", IrOBEX 1.2 |
michael@0 | 759 | // [opcode:1][length:2][Headers:var] |
michael@0 | 760 | frameHeaderLength = 3; |
michael@0 | 761 | |
michael@0 | 762 | mPacketLength = ((((int)aMessage->mData[1]) << 8) | aMessage->mData[2]) - |
michael@0 | 763 | frameHeaderLength; |
michael@0 | 764 | /** |
michael@0 | 765 | * A PUT request from remote devices may be divided into multiple parts. |
michael@0 | 766 | * In other words, one request may need to be received multiple times, |
michael@0 | 767 | * so here we keep a variable mPutPacketReceivedLength to indicate if |
michael@0 | 768 | * current PUT request is done. |
michael@0 | 769 | */ |
michael@0 | 770 | mReceivedDataBuffer = new uint8_t[mPacketLength]; |
michael@0 | 771 | mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal); |
michael@0 | 772 | } |
michael@0 | 773 | |
michael@0 | 774 | int dataLength = aMessage->mSize - frameHeaderLength; |
michael@0 | 775 | |
michael@0 | 776 | // Check length before memcpy to prevent from memory pollution |
michael@0 | 777 | if (dataLength < 0 || |
michael@0 | 778 | mPutPacketReceivedLength + dataLength > mPacketLength) { |
michael@0 | 779 | BT_LOGR("Received packet size is unreasonable"); |
michael@0 | 780 | |
michael@0 | 781 | ReplyToPut(mPutFinalFlag, false); |
michael@0 | 782 | DeleteReceivedFile(); |
michael@0 | 783 | FileTransferComplete(); |
michael@0 | 784 | |
michael@0 | 785 | return false; |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | memcpy(mReceivedDataBuffer.get() + mPutPacketReceivedLength, |
michael@0 | 789 | &aMessage->mData[frameHeaderLength], dataLength); |
michael@0 | 790 | |
michael@0 | 791 | mPutPacketReceivedLength += dataLength; |
michael@0 | 792 | |
michael@0 | 793 | return (mPutPacketReceivedLength == mPacketLength); |
michael@0 | 794 | } |
michael@0 | 795 | |
michael@0 | 796 | void |
michael@0 | 797 | BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage) |
michael@0 | 798 | { |
michael@0 | 799 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 800 | |
michael@0 | 801 | uint8_t opCode; |
michael@0 | 802 | int receivedLength = aMessage->mSize; |
michael@0 | 803 | |
michael@0 | 804 | if (mPutPacketReceivedLength > 0) { |
michael@0 | 805 | opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put; |
michael@0 | 806 | } else { |
michael@0 | 807 | opCode = aMessage->mData[0]; |
michael@0 | 808 | |
michael@0 | 809 | // When there's a Put packet right after a PutFinal packet, |
michael@0 | 810 | // which means it's the start point of a new file. |
michael@0 | 811 | if (mPutFinalFlag && |
michael@0 | 812 | (opCode == ObexRequestCode::Put || |
michael@0 | 813 | opCode == ObexRequestCode::PutFinal)) { |
michael@0 | 814 | mNewFileFlag = true; |
michael@0 | 815 | AfterFirstPut(); |
michael@0 | 816 | } |
michael@0 | 817 | } |
michael@0 | 818 | |
michael@0 | 819 | ObexHeaderSet pktHeaders(opCode); |
michael@0 | 820 | if (opCode == ObexRequestCode::Connect) { |
michael@0 | 821 | // Section 3.3.1 "Connect", IrOBEX 1.2 |
michael@0 | 822 | // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] |
michael@0 | 823 | // [Headers:var] |
michael@0 | 824 | if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) { |
michael@0 | 825 | ReplyError(ObexResponseCode::BadRequest); |
michael@0 | 826 | return; |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | ReplyToConnect(); |
michael@0 | 830 | AfterOppConnected(); |
michael@0 | 831 | } else if (opCode == ObexRequestCode::Abort) { |
michael@0 | 832 | // Section 3.3.5 "Abort", IrOBEX 1.2 |
michael@0 | 833 | // [opcode:1][length:2][Headers:var] |
michael@0 | 834 | if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) { |
michael@0 | 835 | ReplyError(ObexResponseCode::BadRequest); |
michael@0 | 836 | return; |
michael@0 | 837 | } |
michael@0 | 838 | |
michael@0 | 839 | ReplyToDisconnectOrAbort(); |
michael@0 | 840 | DeleteReceivedFile(); |
michael@0 | 841 | } else if (opCode == ObexRequestCode::Disconnect) { |
michael@0 | 842 | // Section 3.3.2 "Disconnect", IrOBEX 1.2 |
michael@0 | 843 | // [opcode:1][length:2][Headers:var] |
michael@0 | 844 | if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) { |
michael@0 | 845 | ReplyError(ObexResponseCode::BadRequest); |
michael@0 | 846 | return; |
michael@0 | 847 | } |
michael@0 | 848 | |
michael@0 | 849 | ReplyToDisconnectOrAbort(); |
michael@0 | 850 | AfterOppDisconnected(); |
michael@0 | 851 | FileTransferComplete(); |
michael@0 | 852 | } else if (opCode == ObexRequestCode::Put || |
michael@0 | 853 | opCode == ObexRequestCode::PutFinal) { |
michael@0 | 854 | if (!ComposePacket(opCode, aMessage)) { |
michael@0 | 855 | return; |
michael@0 | 856 | } |
michael@0 | 857 | |
michael@0 | 858 | // A Put packet is received completely |
michael@0 | 859 | ParseHeaders(mReceivedDataBuffer.get(), |
michael@0 | 860 | mPutPacketReceivedLength, &pktHeaders); |
michael@0 | 861 | ExtractPacketHeaders(pktHeaders); |
michael@0 | 862 | ValidateFileName(); |
michael@0 | 863 | |
michael@0 | 864 | // When we cancel the transfer, delete the file and notify completion |
michael@0 | 865 | if (mAbortFlag) { |
michael@0 | 866 | ReplyToPut(mPutFinalFlag, false); |
michael@0 | 867 | mSentFileLength += mBodySegmentLength; |
michael@0 | 868 | DeleteReceivedFile(); |
michael@0 | 869 | FileTransferComplete(); |
michael@0 | 870 | return; |
michael@0 | 871 | } |
michael@0 | 872 | |
michael@0 | 873 | // Wait until get confirmation from user, then create file and write to it |
michael@0 | 874 | if (mWaitingForConfirmationFlag) { |
michael@0 | 875 | ReceivingFileConfirmation(); |
michael@0 | 876 | mSentFileLength += mBodySegmentLength; |
michael@0 | 877 | return; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | // Already get confirmation from user, create a new file if needed and |
michael@0 | 881 | // write to output stream |
michael@0 | 882 | if (mNewFileFlag) { |
michael@0 | 883 | StartFileTransfer(); |
michael@0 | 884 | if (!CreateFile()) { |
michael@0 | 885 | ReplyToPut(mPutFinalFlag, false); |
michael@0 | 886 | return; |
michael@0 | 887 | } |
michael@0 | 888 | mNewFileFlag = false; |
michael@0 | 889 | } |
michael@0 | 890 | |
michael@0 | 891 | if (!WriteToFile(mBodySegment.get(), mBodySegmentLength)) { |
michael@0 | 892 | ReplyToPut(mPutFinalFlag, false); |
michael@0 | 893 | return; |
michael@0 | 894 | } |
michael@0 | 895 | |
michael@0 | 896 | ReplyToPut(mPutFinalFlag, true); |
michael@0 | 897 | |
michael@0 | 898 | // Send progress update |
michael@0 | 899 | mSentFileLength += mBodySegmentLength; |
michael@0 | 900 | if (mSentFileLength > kUpdateProgressBase * mUpdateProgressCounter) { |
michael@0 | 901 | UpdateProgress(); |
michael@0 | 902 | mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; |
michael@0 | 903 | } |
michael@0 | 904 | |
michael@0 | 905 | // Success to receive a file and notify completion |
michael@0 | 906 | if (mPutFinalFlag) { |
michael@0 | 907 | mSuccessFlag = true; |
michael@0 | 908 | FileTransferComplete(); |
michael@0 | 909 | NotifyAboutFileChange(); |
michael@0 | 910 | } |
michael@0 | 911 | } else if (opCode == ObexRequestCode::Get || |
michael@0 | 912 | opCode == ObexRequestCode::GetFinal || |
michael@0 | 913 | opCode == ObexRequestCode::SetPath) { |
michael@0 | 914 | ReplyError(ObexResponseCode::BadRequest); |
michael@0 | 915 | BT_WARNING("Unsupported ObexRequestCode"); |
michael@0 | 916 | } else { |
michael@0 | 917 | ReplyError(ObexResponseCode::NotImplemented); |
michael@0 | 918 | BT_WARNING("Unrecognized ObexRequestCode"); |
michael@0 | 919 | } |
michael@0 | 920 | } |
michael@0 | 921 | |
michael@0 | 922 | void |
michael@0 | 923 | BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage) |
michael@0 | 924 | { |
michael@0 | 925 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 926 | |
michael@0 | 927 | uint8_t opCode = aMessage->mData[0]; |
michael@0 | 928 | |
michael@0 | 929 | // Check response code and send out system message as finished if the response |
michael@0 | 930 | // code is somehow incorrect. |
michael@0 | 931 | uint8_t expectedOpCode = ObexResponseCode::Success; |
michael@0 | 932 | if (mLastCommand == ObexRequestCode::Put) { |
michael@0 | 933 | expectedOpCode = ObexResponseCode::Continue; |
michael@0 | 934 | } |
michael@0 | 935 | |
michael@0 | 936 | if (opCode != expectedOpCode) { |
michael@0 | 937 | if (mLastCommand == ObexRequestCode::Put || |
michael@0 | 938 | mLastCommand == ObexRequestCode::Abort || |
michael@0 | 939 | mLastCommand == ObexRequestCode::PutFinal) { |
michael@0 | 940 | SendDisconnectRequest(); |
michael@0 | 941 | } |
michael@0 | 942 | nsAutoCString str; |
michael@0 | 943 | str += "[OPP] 0x"; |
michael@0 | 944 | str.AppendInt(mLastCommand, 16); |
michael@0 | 945 | str += " failed"; |
michael@0 | 946 | BT_WARNING(str.get()); |
michael@0 | 947 | FileTransferComplete(); |
michael@0 | 948 | return; |
michael@0 | 949 | } |
michael@0 | 950 | |
michael@0 | 951 | if (mLastCommand == ObexRequestCode::PutFinal) { |
michael@0 | 952 | mSuccessFlag = true; |
michael@0 | 953 | FileTransferComplete(); |
michael@0 | 954 | |
michael@0 | 955 | if (mInputStream) { |
michael@0 | 956 | mInputStream->Close(); |
michael@0 | 957 | mInputStream = nullptr; |
michael@0 | 958 | } |
michael@0 | 959 | |
michael@0 | 960 | if (mCurrentBlobIndex + 1 == (int) mBatches[0].mBlobs.Length()) { |
michael@0 | 961 | SendDisconnectRequest(); |
michael@0 | 962 | } else { |
michael@0 | 963 | StartSendingNextFile(); |
michael@0 | 964 | } |
michael@0 | 965 | } else if (mLastCommand == ObexRequestCode::Abort) { |
michael@0 | 966 | SendDisconnectRequest(); |
michael@0 | 967 | FileTransferComplete(); |
michael@0 | 968 | } else if (mLastCommand == ObexRequestCode::Disconnect) { |
michael@0 | 969 | AfterOppDisconnected(); |
michael@0 | 970 | // Most devices will directly terminate connection after receiving |
michael@0 | 971 | // Disconnect request, so we make a delay here. If the socket hasn't been |
michael@0 | 972 | // disconnected, we will close it. |
michael@0 | 973 | if (mSocket) { |
michael@0 | 974 | MessageLoop::current()-> |
michael@0 | 975 | PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000); |
michael@0 | 976 | } |
michael@0 | 977 | } else if (mLastCommand == ObexRequestCode::Connect) { |
michael@0 | 978 | MOZ_ASSERT(!mFileName.IsEmpty()); |
michael@0 | 979 | MOZ_ASSERT(mBlob); |
michael@0 | 980 | |
michael@0 | 981 | AfterOppConnected(); |
michael@0 | 982 | |
michael@0 | 983 | // Keep remote information |
michael@0 | 984 | mRemoteObexVersion = aMessage->mData[3]; |
michael@0 | 985 | mRemoteConnectionFlags = aMessage->mData[4]; |
michael@0 | 986 | mRemoteMaxPacketLength = |
michael@0 | 987 | (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]); |
michael@0 | 988 | |
michael@0 | 989 | // The length of file name exceeds maximum length. |
michael@0 | 990 | int fileNameByteLen = (mFileName.Length() + 1) * 2; |
michael@0 | 991 | int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize; |
michael@0 | 992 | if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) { |
michael@0 | 993 | BT_WARNING("The length of file name is aberrant."); |
michael@0 | 994 | SendDisconnectRequest(); |
michael@0 | 995 | return; |
michael@0 | 996 | } |
michael@0 | 997 | |
michael@0 | 998 | SendPutHeaderRequest(mFileName, mFileLength); |
michael@0 | 999 | } else if (mLastCommand == ObexRequestCode::Put) { |
michael@0 | 1000 | if (mWaitingToSendPutFinal) { |
michael@0 | 1001 | SendPutFinalRequest(); |
michael@0 | 1002 | return; |
michael@0 | 1003 | } |
michael@0 | 1004 | |
michael@0 | 1005 | if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) { |
michael@0 | 1006 | UpdateProgress(); |
michael@0 | 1007 | mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; |
michael@0 | 1008 | } |
michael@0 | 1009 | |
michael@0 | 1010 | nsresult rv; |
michael@0 | 1011 | if (!mInputStream) { |
michael@0 | 1012 | rv = mBlob->GetInternalStream(getter_AddRefs(mInputStream)); |
michael@0 | 1013 | if (NS_FAILED(rv)) { |
michael@0 | 1014 | BT_WARNING("Can't get internal stream of blob"); |
michael@0 | 1015 | SendDisconnectRequest(); |
michael@0 | 1016 | return; |
michael@0 | 1017 | } |
michael@0 | 1018 | } |
michael@0 | 1019 | |
michael@0 | 1020 | nsRefPtr<ReadFileTask> task = new ReadFileTask(mInputStream, |
michael@0 | 1021 | mRemoteMaxPacketLength); |
michael@0 | 1022 | rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL); |
michael@0 | 1023 | if (NS_FAILED(rv)) { |
michael@0 | 1024 | BT_WARNING("Cannot dispatch read file task!"); |
michael@0 | 1025 | SendDisconnectRequest(); |
michael@0 | 1026 | } |
michael@0 | 1027 | } else { |
michael@0 | 1028 | BT_WARNING("Unhandled ObexRequestCode"); |
michael@0 | 1029 | } |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | // Virtual function of class SocketConsumer |
michael@0 | 1033 | void |
michael@0 | 1034 | BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket, |
michael@0 | 1035 | nsAutoPtr<UnixSocketRawData>& aMessage) |
michael@0 | 1036 | { |
michael@0 | 1037 | if (mIsServer) { |
michael@0 | 1038 | ServerDataHandler(aMessage); |
michael@0 | 1039 | } else { |
michael@0 | 1040 | ClientDataHandler(aMessage); |
michael@0 | 1041 | } |
michael@0 | 1042 | } |
michael@0 | 1043 | |
michael@0 | 1044 | void |
michael@0 | 1045 | BluetoothOppManager::SendConnectRequest() |
michael@0 | 1046 | { |
michael@0 | 1047 | if (mConnected) return; |
michael@0 | 1048 | |
michael@0 | 1049 | // Section 3.3.1 "Connect", IrOBEX 1.2 |
michael@0 | 1050 | // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] |
michael@0 | 1051 | // [Headers:var] |
michael@0 | 1052 | uint8_t req[255]; |
michael@0 | 1053 | int index = 7; |
michael@0 | 1054 | |
michael@0 | 1055 | req[3] = 0x10; // version=1.0 |
michael@0 | 1056 | req[4] = 0x00; // flag=0x00 |
michael@0 | 1057 | req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; |
michael@0 | 1058 | req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; |
michael@0 | 1059 | |
michael@0 | 1060 | SendObexData(req, ObexRequestCode::Connect, index); |
michael@0 | 1061 | } |
michael@0 | 1062 | |
michael@0 | 1063 | void |
michael@0 | 1064 | BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName, |
michael@0 | 1065 | int aFileSize) |
michael@0 | 1066 | { |
michael@0 | 1067 | if (!mConnected) return; |
michael@0 | 1068 | |
michael@0 | 1069 | uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; |
michael@0 | 1070 | |
michael@0 | 1071 | int len = aFileName.Length(); |
michael@0 | 1072 | uint8_t* fileName = new uint8_t[(len + 1) * 2]; |
michael@0 | 1073 | const char16_t* fileNamePtr = aFileName.BeginReading(); |
michael@0 | 1074 | |
michael@0 | 1075 | for (int i = 0; i < len; i++) { |
michael@0 | 1076 | fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8); |
michael@0 | 1077 | fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i]; |
michael@0 | 1078 | } |
michael@0 | 1079 | |
michael@0 | 1080 | fileName[len * 2] = 0x00; |
michael@0 | 1081 | fileName[len * 2 + 1] = 0x00; |
michael@0 | 1082 | |
michael@0 | 1083 | int index = 3; |
michael@0 | 1084 | index += AppendHeaderName(&req[index], mRemoteMaxPacketLength - index, |
michael@0 | 1085 | (char*)fileName, (len + 1) * 2); |
michael@0 | 1086 | index += AppendHeaderLength(&req[index], aFileSize); |
michael@0 | 1087 | |
michael@0 | 1088 | SendObexData(req, ObexRequestCode::Put, index); |
michael@0 | 1089 | |
michael@0 | 1090 | delete [] fileName; |
michael@0 | 1091 | delete [] req; |
michael@0 | 1092 | } |
michael@0 | 1093 | |
michael@0 | 1094 | void |
michael@0 | 1095 | BluetoothOppManager::SendPutRequest(uint8_t* aFileBody, |
michael@0 | 1096 | int aFileBodyLength) |
michael@0 | 1097 | { |
michael@0 | 1098 | if (!mConnected) return; |
michael@0 | 1099 | int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize; |
michael@0 | 1100 | if (aFileBodyLength > packetLeftSpace) { |
michael@0 | 1101 | BT_WARNING("Not allowed such a small MaxPacketLength value"); |
michael@0 | 1102 | return; |
michael@0 | 1103 | } |
michael@0 | 1104 | |
michael@0 | 1105 | // Section 3.3.3 "Put", IrOBEX 1.2 |
michael@0 | 1106 | // [opcode:1][length:2][Headers:var] |
michael@0 | 1107 | uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; |
michael@0 | 1108 | |
michael@0 | 1109 | int index = 3; |
michael@0 | 1110 | index += AppendHeaderBody(&req[index], mRemoteMaxPacketLength - index, |
michael@0 | 1111 | aFileBody, aFileBodyLength); |
michael@0 | 1112 | |
michael@0 | 1113 | SendObexData(req, ObexRequestCode::Put, index); |
michael@0 | 1114 | delete [] req; |
michael@0 | 1115 | |
michael@0 | 1116 | mSentFileLength += aFileBodyLength; |
michael@0 | 1117 | } |
michael@0 | 1118 | |
michael@0 | 1119 | void |
michael@0 | 1120 | BluetoothOppManager::SendPutFinalRequest() |
michael@0 | 1121 | { |
michael@0 | 1122 | if (!mConnected) return; |
michael@0 | 1123 | |
michael@0 | 1124 | /** |
michael@0 | 1125 | * Section 2.2.9, "End-of-Body", IrObex 1.2 |
michael@0 | 1126 | * End-of-Body is used to identify the last chunk of the object body. |
michael@0 | 1127 | * For most platforms, a PutFinal packet is sent with an zero length |
michael@0 | 1128 | * End-of-Body header. |
michael@0 | 1129 | */ |
michael@0 | 1130 | |
michael@0 | 1131 | // [opcode:1][length:2] |
michael@0 | 1132 | int index = 3; |
michael@0 | 1133 | uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; |
michael@0 | 1134 | index += AppendHeaderEndOfBody(&req[index]); |
michael@0 | 1135 | |
michael@0 | 1136 | SendObexData(req, ObexRequestCode::PutFinal, index); |
michael@0 | 1137 | delete [] req; |
michael@0 | 1138 | |
michael@0 | 1139 | mWaitingToSendPutFinal = false; |
michael@0 | 1140 | } |
michael@0 | 1141 | |
michael@0 | 1142 | void |
michael@0 | 1143 | BluetoothOppManager::SendDisconnectRequest() |
michael@0 | 1144 | { |
michael@0 | 1145 | if (!mConnected) return; |
michael@0 | 1146 | |
michael@0 | 1147 | // Section 3.3.2 "Disconnect", IrOBEX 1.2 |
michael@0 | 1148 | // [opcode:1][length:2][Headers:var] |
michael@0 | 1149 | uint8_t req[255]; |
michael@0 | 1150 | int index = 3; |
michael@0 | 1151 | |
michael@0 | 1152 | SendObexData(req, ObexRequestCode::Disconnect, index); |
michael@0 | 1153 | } |
michael@0 | 1154 | |
michael@0 | 1155 | void |
michael@0 | 1156 | BluetoothOppManager::CheckPutFinal(uint32_t aNumRead) |
michael@0 | 1157 | { |
michael@0 | 1158 | if (mSentFileLength + aNumRead >= mFileLength) { |
michael@0 | 1159 | mWaitingToSendPutFinal = true; |
michael@0 | 1160 | } |
michael@0 | 1161 | } |
michael@0 | 1162 | |
michael@0 | 1163 | bool |
michael@0 | 1164 | BluetoothOppManager::IsConnected() |
michael@0 | 1165 | { |
michael@0 | 1166 | return (mConnected && !mSendTransferCompleteFlag); |
michael@0 | 1167 | } |
michael@0 | 1168 | |
michael@0 | 1169 | void |
michael@0 | 1170 | BluetoothOppManager::GetAddress(nsAString& aDeviceAddress) |
michael@0 | 1171 | { |
michael@0 | 1172 | return mSocket->GetAddress(aDeviceAddress); |
michael@0 | 1173 | } |
michael@0 | 1174 | |
michael@0 | 1175 | void |
michael@0 | 1176 | BluetoothOppManager::ReplyToConnect() |
michael@0 | 1177 | { |
michael@0 | 1178 | if (mConnected) return; |
michael@0 | 1179 | |
michael@0 | 1180 | // Section 3.3.1 "Connect", IrOBEX 1.2 |
michael@0 | 1181 | // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] |
michael@0 | 1182 | // [Headers:var] |
michael@0 | 1183 | uint8_t req[255]; |
michael@0 | 1184 | int index = 7; |
michael@0 | 1185 | |
michael@0 | 1186 | req[3] = 0x10; // version=1.0 |
michael@0 | 1187 | req[4] = 0x00; // flag=0x00 |
michael@0 | 1188 | req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; |
michael@0 | 1189 | req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; |
michael@0 | 1190 | |
michael@0 | 1191 | SendObexData(req, ObexResponseCode::Success, index); |
michael@0 | 1192 | } |
michael@0 | 1193 | |
michael@0 | 1194 | void |
michael@0 | 1195 | BluetoothOppManager::ReplyToDisconnectOrAbort() |
michael@0 | 1196 | { |
michael@0 | 1197 | if (!mConnected) return; |
michael@0 | 1198 | |
michael@0 | 1199 | // Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2 |
michael@0 | 1200 | // The format of response packet of "Disconnect" and "Abort" are the same |
michael@0 | 1201 | // [opcode:1][length:2][Headers:var] |
michael@0 | 1202 | uint8_t req[255]; |
michael@0 | 1203 | int index = 3; |
michael@0 | 1204 | |
michael@0 | 1205 | SendObexData(req, ObexResponseCode::Success, index); |
michael@0 | 1206 | } |
michael@0 | 1207 | |
michael@0 | 1208 | void |
michael@0 | 1209 | BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue) |
michael@0 | 1210 | { |
michael@0 | 1211 | if (!mConnected) return; |
michael@0 | 1212 | |
michael@0 | 1213 | // The received length can be reset here because this is where we reply to a |
michael@0 | 1214 | // complete put packet. |
michael@0 | 1215 | mPutPacketReceivedLength = 0; |
michael@0 | 1216 | |
michael@0 | 1217 | // Section 3.3.2 "Disconnect", IrOBEX 1.2 |
michael@0 | 1218 | // [opcode:1][length:2][Headers:var] |
michael@0 | 1219 | uint8_t req[255]; |
michael@0 | 1220 | int index = 3; |
michael@0 | 1221 | uint8_t opcode; |
michael@0 | 1222 | |
michael@0 | 1223 | if (aContinue) { |
michael@0 | 1224 | opcode = (aFinal)? ObexResponseCode::Success : |
michael@0 | 1225 | ObexResponseCode::Continue; |
michael@0 | 1226 | } else { |
michael@0 | 1227 | opcode = (aFinal)? ObexResponseCode::Unauthorized : |
michael@0 | 1228 | ObexResponseCode::Unauthorized & (~FINAL_BIT); |
michael@0 | 1229 | } |
michael@0 | 1230 | |
michael@0 | 1231 | SendObexData(req, opcode, index); |
michael@0 | 1232 | } |
michael@0 | 1233 | |
michael@0 | 1234 | void |
michael@0 | 1235 | BluetoothOppManager::ReplyError(uint8_t aError) |
michael@0 | 1236 | { |
michael@0 | 1237 | BT_LOGR("error: %d", aError); |
michael@0 | 1238 | |
michael@0 | 1239 | // Section 3.2 "Response Format", IrOBEX 1.2 |
michael@0 | 1240 | // [opcode:1][length:2][Headers:var] |
michael@0 | 1241 | uint8_t req[255]; |
michael@0 | 1242 | int index = 3; |
michael@0 | 1243 | |
michael@0 | 1244 | SendObexData(req, aError, index); |
michael@0 | 1245 | } |
michael@0 | 1246 | |
michael@0 | 1247 | void |
michael@0 | 1248 | BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize) |
michael@0 | 1249 | { |
michael@0 | 1250 | SetObexPacketInfo(aData, aOpcode, aSize); |
michael@0 | 1251 | |
michael@0 | 1252 | if (!mIsServer) { |
michael@0 | 1253 | mLastCommand = aOpcode; |
michael@0 | 1254 | } |
michael@0 | 1255 | |
michael@0 | 1256 | UnixSocketRawData* s = new UnixSocketRawData(aSize); |
michael@0 | 1257 | memcpy(s->mData, aData, s->mSize); |
michael@0 | 1258 | mSocket->SendDroidSocketData(s); |
michael@0 | 1259 | } |
michael@0 | 1260 | |
michael@0 | 1261 | void |
michael@0 | 1262 | BluetoothOppManager::FileTransferComplete() |
michael@0 | 1263 | { |
michael@0 | 1264 | if (mSendTransferCompleteFlag) { |
michael@0 | 1265 | return; |
michael@0 | 1266 | } |
michael@0 | 1267 | |
michael@0 | 1268 | NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-complete"); |
michael@0 | 1269 | InfallibleTArray<BluetoothNamedValue> parameters; |
michael@0 | 1270 | |
michael@0 | 1271 | BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); |
michael@0 | 1272 | BT_APPEND_NAMED_VALUE(parameters, "success", mSuccessFlag); |
michael@0 | 1273 | BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); |
michael@0 | 1274 | BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); |
michael@0 | 1275 | BT_APPEND_NAMED_VALUE(parameters, "fileLength", mSentFileLength); |
michael@0 | 1276 | BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); |
michael@0 | 1277 | |
michael@0 | 1278 | BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); |
michael@0 | 1279 | |
michael@0 | 1280 | mSendTransferCompleteFlag = true; |
michael@0 | 1281 | } |
michael@0 | 1282 | |
michael@0 | 1283 | void |
michael@0 | 1284 | BluetoothOppManager::StartFileTransfer() |
michael@0 | 1285 | { |
michael@0 | 1286 | NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-start"); |
michael@0 | 1287 | InfallibleTArray<BluetoothNamedValue> parameters; |
michael@0 | 1288 | |
michael@0 | 1289 | BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); |
michael@0 | 1290 | BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); |
michael@0 | 1291 | BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); |
michael@0 | 1292 | BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); |
michael@0 | 1293 | BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); |
michael@0 | 1294 | |
michael@0 | 1295 | BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); |
michael@0 | 1296 | |
michael@0 | 1297 | mSendTransferCompleteFlag = false; |
michael@0 | 1298 | } |
michael@0 | 1299 | |
michael@0 | 1300 | void |
michael@0 | 1301 | BluetoothOppManager::UpdateProgress() |
michael@0 | 1302 | { |
michael@0 | 1303 | NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-update-progress"); |
michael@0 | 1304 | InfallibleTArray<BluetoothNamedValue> parameters; |
michael@0 | 1305 | |
michael@0 | 1306 | BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); |
michael@0 | 1307 | BT_APPEND_NAMED_VALUE(parameters, "received", mIsServer); |
michael@0 | 1308 | BT_APPEND_NAMED_VALUE(parameters, "processedLength", mSentFileLength); |
michael@0 | 1309 | BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); |
michael@0 | 1310 | |
michael@0 | 1311 | BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); |
michael@0 | 1312 | } |
michael@0 | 1313 | |
michael@0 | 1314 | void |
michael@0 | 1315 | BluetoothOppManager::ReceivingFileConfirmation() |
michael@0 | 1316 | { |
michael@0 | 1317 | NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-receiving-file-confirmation"); |
michael@0 | 1318 | InfallibleTArray<BluetoothNamedValue> parameters; |
michael@0 | 1319 | |
michael@0 | 1320 | BT_APPEND_NAMED_VALUE(parameters, "address", mDeviceAddress); |
michael@0 | 1321 | BT_APPEND_NAMED_VALUE(parameters, "fileName", mFileName); |
michael@0 | 1322 | BT_APPEND_NAMED_VALUE(parameters, "fileLength", mFileLength); |
michael@0 | 1323 | BT_APPEND_NAMED_VALUE(parameters, "contentType", mContentType); |
michael@0 | 1324 | |
michael@0 | 1325 | BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); |
michael@0 | 1326 | } |
michael@0 | 1327 | |
michael@0 | 1328 | void |
michael@0 | 1329 | BluetoothOppManager::NotifyAboutFileChange() |
michael@0 | 1330 | { |
michael@0 | 1331 | NS_NAMED_LITERAL_STRING(data, "modified"); |
michael@0 | 1332 | |
michael@0 | 1333 | nsCOMPtr<nsIObserverService> obs = |
michael@0 | 1334 | mozilla::services::GetObserverService(); |
michael@0 | 1335 | NS_ENSURE_TRUE_VOID(obs); |
michael@0 | 1336 | |
michael@0 | 1337 | obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get()); |
michael@0 | 1338 | } |
michael@0 | 1339 | |
michael@0 | 1340 | void |
michael@0 | 1341 | BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket) |
michael@0 | 1342 | { |
michael@0 | 1343 | BT_LOGR("[%s]", (mIsServer)? "server" : "client"); |
michael@0 | 1344 | MOZ_ASSERT(aSocket); |
michael@0 | 1345 | |
michael@0 | 1346 | /** |
michael@0 | 1347 | * If the created connection is an inbound connection, close server socket |
michael@0 | 1348 | * because currently only one file-transfer session is allowed. After that, |
michael@0 | 1349 | * we need to make sure that server socket would be nulled out. |
michael@0 | 1350 | * As for outbound connections, we just notify the controller that it's done. |
michael@0 | 1351 | */ |
michael@0 | 1352 | if (aSocket == mServerSocket) { |
michael@0 | 1353 | MOZ_ASSERT(!mSocket); |
michael@0 | 1354 | mServerSocket.swap(mSocket); |
michael@0 | 1355 | } |
michael@0 | 1356 | |
michael@0 | 1357 | // Cache device address since we can't get socket address when a remote |
michael@0 | 1358 | // device disconnect with us. |
michael@0 | 1359 | mSocket->GetAddress(mDeviceAddress); |
michael@0 | 1360 | |
michael@0 | 1361 | // Start sending file if we connect as a client |
michael@0 | 1362 | if (!mIsServer) { |
michael@0 | 1363 | StartSendingNextFile(); |
michael@0 | 1364 | } |
michael@0 | 1365 | } |
michael@0 | 1366 | |
michael@0 | 1367 | void |
michael@0 | 1368 | BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket) |
michael@0 | 1369 | { |
michael@0 | 1370 | BT_LOGR("[%s]", (mIsServer)? "server" : "client"); |
michael@0 | 1371 | |
michael@0 | 1372 | mServerSocket = nullptr; |
michael@0 | 1373 | mSocket = nullptr; |
michael@0 | 1374 | |
michael@0 | 1375 | if (!mIsServer) { |
michael@0 | 1376 | // Inform gaia of remaining blobs' sending failure |
michael@0 | 1377 | DiscardBlobsToSend(); |
michael@0 | 1378 | } |
michael@0 | 1379 | |
michael@0 | 1380 | // Listen as a server if there's no more batch to process |
michael@0 | 1381 | if (!ProcessNextBatch()) { |
michael@0 | 1382 | Listen(); |
michael@0 | 1383 | } |
michael@0 | 1384 | } |
michael@0 | 1385 | |
michael@0 | 1386 | void |
michael@0 | 1387 | BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) |
michael@0 | 1388 | { |
michael@0 | 1389 | MOZ_ASSERT(aSocket); |
michael@0 | 1390 | if (aSocket != mSocket) { |
michael@0 | 1391 | // Do nothing when a listening server socket is closed. |
michael@0 | 1392 | return; |
michael@0 | 1393 | } |
michael@0 | 1394 | BT_LOGR("[%s]", (mIsServer) ? "server" : "client"); |
michael@0 | 1395 | |
michael@0 | 1396 | /** |
michael@0 | 1397 | * It is valid for a bluetooth device which is transfering file via OPP |
michael@0 | 1398 | * closing socket without sending OBEX disconnect request first. So we |
michael@0 | 1399 | * delete the broken file when we failed to receive a file from the remote, |
michael@0 | 1400 | * and notify the transfer has been completed (but failed). We also call |
michael@0 | 1401 | * AfterOppDisconnected here to ensure all variables will be cleaned. |
michael@0 | 1402 | */ |
michael@0 | 1403 | if (!mSuccessFlag) { |
michael@0 | 1404 | if (mIsServer) { |
michael@0 | 1405 | DeleteReceivedFile(); |
michael@0 | 1406 | } |
michael@0 | 1407 | |
michael@0 | 1408 | FileTransferComplete(); |
michael@0 | 1409 | if (!mIsServer) { |
michael@0 | 1410 | // Inform gaia of remaining blobs' sending failure |
michael@0 | 1411 | DiscardBlobsToSend(); |
michael@0 | 1412 | } |
michael@0 | 1413 | } |
michael@0 | 1414 | |
michael@0 | 1415 | AfterOppDisconnected(); |
michael@0 | 1416 | mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); |
michael@0 | 1417 | mSuccessFlag = false; |
michael@0 | 1418 | |
michael@0 | 1419 | mSocket = nullptr; |
michael@0 | 1420 | // Listen as a server if there's no more batch to process |
michael@0 | 1421 | if (!ProcessNextBatch()) { |
michael@0 | 1422 | Listen(); |
michael@0 | 1423 | } |
michael@0 | 1424 | } |
michael@0 | 1425 | |
michael@0 | 1426 | void |
michael@0 | 1427 | BluetoothOppManager::Disconnect(BluetoothProfileController* aController) |
michael@0 | 1428 | { |
michael@0 | 1429 | if (mSocket) { |
michael@0 | 1430 | mSocket->Disconnect(); |
michael@0 | 1431 | } else { |
michael@0 | 1432 | BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__); |
michael@0 | 1433 | } |
michael@0 | 1434 | } |
michael@0 | 1435 | |
michael@0 | 1436 | NS_IMPL_ISUPPORTS(BluetoothOppManager, nsIObserver) |
michael@0 | 1437 | |
michael@0 | 1438 | bool |
michael@0 | 1439 | BluetoothOppManager::AcquireSdcardMountLock() |
michael@0 | 1440 | { |
michael@0 | 1441 | nsCOMPtr<nsIVolumeService> volumeSrv = |
michael@0 | 1442 | do_GetService(NS_VOLUMESERVICE_CONTRACTID); |
michael@0 | 1443 | NS_ENSURE_TRUE(volumeSrv, false); |
michael@0 | 1444 | nsresult rv; |
michael@0 | 1445 | rv = volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"), |
michael@0 | 1446 | getter_AddRefs(mMountLock)); |
michael@0 | 1447 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 1448 | return true; |
michael@0 | 1449 | } |
michael@0 | 1450 | |
michael@0 | 1451 | void |
michael@0 | 1452 | BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress, |
michael@0 | 1453 | const nsAString& aServiceUuid, |
michael@0 | 1454 | int aChannel) |
michael@0 | 1455 | { |
michael@0 | 1456 | MOZ_ASSERT(false); |
michael@0 | 1457 | } |
michael@0 | 1458 | |
michael@0 | 1459 | void |
michael@0 | 1460 | BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) |
michael@0 | 1461 | { |
michael@0 | 1462 | MOZ_ASSERT(false); |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | void |
michael@0 | 1466 | BluetoothOppManager::Connect(const nsAString& aDeviceAddress, |
michael@0 | 1467 | BluetoothProfileController* aController) |
michael@0 | 1468 | { |
michael@0 | 1469 | MOZ_ASSERT(false); |
michael@0 | 1470 | } |
michael@0 | 1471 | |
michael@0 | 1472 | void |
michael@0 | 1473 | BluetoothOppManager::OnConnect(const nsAString& aErrorStr) |
michael@0 | 1474 | { |
michael@0 | 1475 | MOZ_ASSERT(false); |
michael@0 | 1476 | } |
michael@0 | 1477 | |
michael@0 | 1478 | void |
michael@0 | 1479 | BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr) |
michael@0 | 1480 | { |
michael@0 | 1481 | MOZ_ASSERT(false); |
michael@0 | 1482 | } |
michael@0 | 1483 | |
michael@0 | 1484 | void |
michael@0 | 1485 | BluetoothOppManager::Reset() |
michael@0 | 1486 | { |
michael@0 | 1487 | MOZ_ASSERT(false); |
michael@0 | 1488 | } |