michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "BluetoothSocket.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/message_loop.h" michael@0: #include "BluetoothSocketObserver.h" michael@0: #include "BluetoothUtils.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #define FIRST_SOCKET_INFO_MSG_LENGTH 4 michael@0: #define TOTAL_SOCKET_INFO_LENGTH 20 michael@0: michael@0: using namespace mozilla::ipc; michael@0: USING_BLUETOOTH_NAMESPACE michael@0: michael@0: static const size_t MAX_READ_SIZE = 1 << 16; michael@0: static const uint8_t UUID_OBEX_OBJECT_PUSH[] = { michael@0: 0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00, michael@0: 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB michael@0: }; michael@0: static const btsock_interface_t* sBluetoothSocketInterface = nullptr; michael@0: michael@0: // helper functions michael@0: static bool michael@0: EnsureBluetoothSocketHalLoad() michael@0: { michael@0: if (sBluetoothSocketInterface) { michael@0: return true; michael@0: } michael@0: michael@0: const bt_interface_t* btInf = GetBluetoothInterface(); michael@0: NS_ENSURE_TRUE(btInf, false); michael@0: michael@0: sBluetoothSocketInterface = michael@0: (btsock_interface_t *) btInf->get_profile_interface(BT_PROFILE_SOCKETS_ID); michael@0: NS_ENSURE_TRUE(sBluetoothSocketInterface, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static int16_t michael@0: ReadInt16(const uint8_t* aData, size_t* aOffset) michael@0: { michael@0: int16_t value = (aData[*aOffset + 1] << 8) | aData[*aOffset]; michael@0: michael@0: *aOffset += 2; michael@0: return value; michael@0: } michael@0: michael@0: static int32_t michael@0: ReadInt32(const uint8_t* aData, size_t* aOffset) michael@0: { michael@0: int32_t value = (aData[*aOffset + 3] << 24) | michael@0: (aData[*aOffset + 2] << 16) | michael@0: (aData[*aOffset + 1] << 8) | michael@0: aData[*aOffset]; michael@0: *aOffset += 4; michael@0: return value; michael@0: } michael@0: michael@0: static void michael@0: ReadBdAddress(const uint8_t* aData, size_t* aOffset, nsAString& aDeviceAddress) michael@0: { michael@0: char bdstr[18]; michael@0: sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x", michael@0: aData[*aOffset], aData[*aOffset + 1], aData[*aOffset + 2], michael@0: aData[*aOffset + 3], aData[*aOffset + 4], aData[*aOffset + 5]); michael@0: michael@0: aDeviceAddress.AssignLiteral(bdstr); michael@0: *aOffset += 6; michael@0: } michael@0: michael@0: class mozilla::dom::bluetooth::DroidSocketImpl michael@0: : public MessageLoopForIO::Watcher michael@0: { michael@0: public: michael@0: DroidSocketImpl(BluetoothSocket* aConsumer, int aFd) michael@0: : mConsumer(aConsumer) michael@0: , mReadMsgForClientFd(false) michael@0: , mIOLoop(nullptr) michael@0: , mFd(aFd) michael@0: , mShuttingDownOnIOThread(false) michael@0: , mChannel(0) michael@0: , mAuth(false) michael@0: , mEncrypt(false) michael@0: { michael@0: } michael@0: michael@0: DroidSocketImpl(BluetoothSocket* aConsumer, michael@0: int aChannel, bool aAuth, bool aEncrypt) michael@0: : mConsumer(aConsumer) michael@0: , mReadMsgForClientFd(false) michael@0: , mIOLoop(nullptr) michael@0: , mFd(-1) michael@0: , mShuttingDownOnIOThread(false) michael@0: , mChannel(aChannel) michael@0: , mAuth(aAuth) michael@0: , mEncrypt(aEncrypt) michael@0: { } michael@0: michael@0: DroidSocketImpl(BluetoothSocket* aConsumer, const nsAString& aDeviceAddress, michael@0: int aChannel, bool aAuth, bool aEncrypt) michael@0: : mConsumer(aConsumer) michael@0: , mReadMsgForClientFd(false) michael@0: , mIOLoop(nullptr) michael@0: , mFd(-1) michael@0: , mShuttingDownOnIOThread(false) michael@0: , mDeviceAddress(aDeviceAddress) michael@0: , mChannel(aChannel) michael@0: , mAuth(aAuth) michael@0: , mEncrypt(aEncrypt) michael@0: { michael@0: MOZ_ASSERT(!mDeviceAddress.IsEmpty()); michael@0: } michael@0: michael@0: ~DroidSocketImpl() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: void QueueWriteData(UnixSocketRawData* aData) michael@0: { michael@0: mOutgoingQ.AppendElement(aData); michael@0: OnFileCanWriteWithoutBlocking(mFd); michael@0: } michael@0: michael@0: bool IsShutdownOnMainThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mConsumer == nullptr; michael@0: } michael@0: michael@0: bool IsShutdownOnIOThread() michael@0: { michael@0: return mShuttingDownOnIOThread; michael@0: } michael@0: michael@0: void ShutdownOnMainThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!IsShutdownOnMainThread()); michael@0: mConsumer = nullptr; michael@0: } michael@0: michael@0: void ShutdownOnIOThread() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mShuttingDownOnIOThread); michael@0: michael@0: mReadWatcher.StopWatchingFileDescriptor(); michael@0: mWriteWatcher.StopWatchingFileDescriptor(); michael@0: michael@0: mShuttingDownOnIOThread = true; michael@0: } michael@0: michael@0: void Connect(); michael@0: void Listen(); michael@0: michael@0: void SetUpIO(bool aWrite) michael@0: { michael@0: MOZ_ASSERT(!mIOLoop); michael@0: MOZ_ASSERT(mFd >= 0); michael@0: mIOLoop = MessageLoopForIO::current(); michael@0: michael@0: // Set up a read watch michael@0: mIOLoop->WatchFileDescriptor(mFd, michael@0: true, michael@0: MessageLoopForIO::WATCH_READ, michael@0: &mReadWatcher, michael@0: this); michael@0: michael@0: if (aWrite) { michael@0: // Set up a write watch michael@0: mIOLoop->WatchFileDescriptor(mFd.get(), michael@0: false, michael@0: MessageLoopForIO::WATCH_WRITE, michael@0: &mWriteWatcher, michael@0: this); michael@0: } michael@0: } michael@0: michael@0: void ConnectClientFd() michael@0: { michael@0: // Stop current read watch michael@0: mReadWatcher.StopWatchingFileDescriptor(); michael@0: mIOLoop = nullptr; michael@0: michael@0: // Restart read & write watch on client fd michael@0: SetUpIO(true); michael@0: } michael@0: michael@0: /** michael@0: * Consumer pointer. Non-thread safe RefPtr, so should only be manipulated michael@0: * directly from main thread. All non-main-thread accesses should happen with michael@0: * mImpl as container. michael@0: */ michael@0: RefPtr mConsumer; michael@0: michael@0: /** michael@0: * If true, read message header to get client fd. michael@0: */ michael@0: bool mReadMsgForClientFd; michael@0: michael@0: private: michael@0: /** michael@0: * libevent triggered functions that reads data from socket when available and michael@0: * guarenteed non-blocking. Only to be called on IO thread. michael@0: * michael@0: * @param aFd [in] File descriptor to read from michael@0: */ michael@0: virtual void OnFileCanReadWithoutBlocking(int aFd); michael@0: michael@0: /** michael@0: * libevent or developer triggered functions that writes data to socket when michael@0: * available and guarenteed non-blocking. Only to be called on IO thread. michael@0: * michael@0: * @param aFd [in] File descriptor to read from michael@0: */ michael@0: virtual void OnFileCanWriteWithoutBlocking(int aFd); michael@0: michael@0: /** michael@0: * Read message to get data and client fd wrapped in message header michael@0: * michael@0: * @param aFd [in] File descriptor to read message from michael@0: * @param aBuffer [out] Data buffer read michael@0: * @param aLength [out] Number of bytes read michael@0: */ michael@0: ssize_t ReadMsg(int aFd, void *aBuffer, size_t aLength); michael@0: michael@0: /** michael@0: * IO Loop pointer. Must be initalized and called from IO thread only. michael@0: */ michael@0: MessageLoopForIO* mIOLoop; michael@0: michael@0: /** michael@0: * Raw data queue. Must be pushed/popped from IO thread only. michael@0: */ michael@0: typedef nsTArray UnixSocketRawDataQueue; michael@0: UnixSocketRawDataQueue mOutgoingQ; michael@0: michael@0: /** michael@0: * Read watcher for libevent. Only to be accessed on IO Thread. michael@0: */ michael@0: MessageLoopForIO::FileDescriptorWatcher mReadWatcher; michael@0: michael@0: /** michael@0: * Write watcher for libevent. Only to be accessed on IO Thread. michael@0: */ michael@0: MessageLoopForIO::FileDescriptorWatcher mWriteWatcher; michael@0: michael@0: /** michael@0: * File descriptor to read from/write to. Connection happens on user provided michael@0: * thread. Read/write/close happens on IO thread. michael@0: */ michael@0: mozilla::ScopedClose mFd; michael@0: michael@0: /** michael@0: * If true, do not requeue whatever task we're running michael@0: */ michael@0: bool mShuttingDownOnIOThread; michael@0: michael@0: nsString mDeviceAddress; michael@0: int mChannel; michael@0: bool mAuth; michael@0: bool mEncrypt; michael@0: }; michael@0: michael@0: template michael@0: class DeleteInstanceRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: DeleteInstanceRunnable(T* aInstance) michael@0: : mInstance(aInstance) michael@0: { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: delete mInstance; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: T* mInstance; michael@0: }; michael@0: michael@0: class RequestClosingSocketTask : public nsRunnable michael@0: { michael@0: public: michael@0: RequestClosingSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl) michael@0: { michael@0: MOZ_ASSERT(aImpl); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mImpl->IsShutdownOnMainThread()) { michael@0: NS_WARNING("CloseSocket has already been called!"); michael@0: // Since we've already explicitly closed and the close happened before michael@0: // this, this isn't really an error. Since we've warned, return OK. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Start from here, same handling flow as calling CloseSocket() from michael@0: // upper layer michael@0: mImpl->mConsumer->CloseDroidSocket(); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: DroidSocketImpl* mImpl; michael@0: }; michael@0: michael@0: class ShutdownSocketTask : public Task { michael@0: virtual void Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // At this point, there should be no new events on the IO thread after this michael@0: // one with the possible exception of a SocketAcceptTask that michael@0: // ShutdownOnIOThread will cancel for us. We are now fully shut down, so we michael@0: // can send a message to the main thread that will delete mImpl safely knowing michael@0: // that no more tasks reference it. michael@0: mImpl->ShutdownOnIOThread(); michael@0: michael@0: nsRefPtr t(new DeleteInstanceRunnable< michael@0: mozilla::dom::bluetooth::DroidSocketImpl>(mImpl)); michael@0: nsresult rv = NS_DispatchToMainThread(t); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: DroidSocketImpl* mImpl; michael@0: michael@0: public: michael@0: ShutdownSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { } michael@0: }; michael@0: michael@0: class SocketReceiveTask : public nsRunnable michael@0: { michael@0: public: michael@0: SocketReceiveTask(DroidSocketImpl* aImpl, UnixSocketRawData* aData) : michael@0: mImpl(aImpl), michael@0: mRawData(aData) michael@0: { michael@0: MOZ_ASSERT(aImpl); michael@0: MOZ_ASSERT(aData); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mImpl->IsShutdownOnMainThread()) { michael@0: NS_WARNING("mConsumer is null, aborting receive!"); michael@0: // Since we've already explicitly closed and the close happened before michael@0: // this, this isn't really an error. Since we've warned, return OK. michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(mImpl->mConsumer); michael@0: mImpl->mConsumer->ReceiveSocketData(mRawData); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: DroidSocketImpl* mImpl; michael@0: nsAutoPtr mRawData; michael@0: }; michael@0: michael@0: class SocketSendTask : public Task michael@0: { michael@0: public: michael@0: SocketSendTask(BluetoothSocket* aConsumer, DroidSocketImpl* aImpl, michael@0: UnixSocketRawData* aData) michael@0: : mConsumer(aConsumer), michael@0: mImpl(aImpl), michael@0: mData(aData) michael@0: { michael@0: MOZ_ASSERT(aConsumer); michael@0: MOZ_ASSERT(aImpl); michael@0: MOZ_ASSERT(aData); michael@0: } michael@0: michael@0: void michael@0: Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mImpl->IsShutdownOnIOThread()); michael@0: michael@0: mImpl->QueueWriteData(mData); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mConsumer; michael@0: DroidSocketImpl* mImpl; michael@0: UnixSocketRawData* mData; michael@0: }; michael@0: michael@0: class DroidSocketImplTask : public CancelableTask michael@0: { michael@0: public: michael@0: DroidSocketImpl* GetDroidSocketImpl() const michael@0: { michael@0: return mDroidSocketImpl; michael@0: } michael@0: void Cancel() MOZ_OVERRIDE michael@0: { michael@0: mDroidSocketImpl = nullptr; michael@0: } michael@0: bool IsCanceled() const michael@0: { michael@0: return !mDroidSocketImpl; michael@0: } michael@0: protected: michael@0: DroidSocketImplTask(DroidSocketImpl* aDroidSocketImpl) michael@0: : mDroidSocketImpl(aDroidSocketImpl) michael@0: { michael@0: MOZ_ASSERT(mDroidSocketImpl); michael@0: } michael@0: private: michael@0: DroidSocketImpl* mDroidSocketImpl; michael@0: }; michael@0: michael@0: class SocketConnectTask : public DroidSocketImplTask michael@0: { michael@0: public: michael@0: SocketConnectTask(DroidSocketImpl* aDroidSocketImpl) michael@0: : DroidSocketImplTask(aDroidSocketImpl) michael@0: { } michael@0: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!IsCanceled()); michael@0: GetDroidSocketImpl()->Connect(); michael@0: } michael@0: }; michael@0: michael@0: class SocketListenTask : public DroidSocketImplTask michael@0: { michael@0: public: michael@0: SocketListenTask(DroidSocketImpl* aDroidSocketImpl) michael@0: : DroidSocketImplTask(aDroidSocketImpl) michael@0: { } michael@0: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: if (!IsCanceled()) { michael@0: GetDroidSocketImpl()->Listen(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: class SocketConnectClientFdTask : public Task michael@0: { michael@0: virtual void Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: mImpl->ConnectClientFd(); michael@0: } michael@0: michael@0: DroidSocketImpl* mImpl; michael@0: public: michael@0: SocketConnectClientFdTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { } michael@0: }; michael@0: michael@0: void michael@0: DroidSocketImpl::Connect() michael@0: { michael@0: MOZ_ASSERT(sBluetoothSocketInterface); michael@0: michael@0: bt_bdaddr_t remoteBdAddress; michael@0: StringToBdAddressType(mDeviceAddress, &remoteBdAddress); michael@0: michael@0: // TODO: uuid as argument michael@0: int fd = -1; michael@0: bt_status_t status = michael@0: sBluetoothSocketInterface->connect(&remoteBdAddress, michael@0: BTSOCK_RFCOMM, michael@0: UUID_OBEX_OBJECT_PUSH, michael@0: mChannel, michael@0: &fd, michael@0: (BTSOCK_FLAG_ENCRYPT * mEncrypt) | michael@0: (BTSOCK_FLAG_AUTH * mAuth)); michael@0: NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS); michael@0: NS_ENSURE_TRUE_VOID(fd >= 0); michael@0: michael@0: mFd = fd; michael@0: michael@0: MOZ_ASSERT(!mIOLoop); michael@0: mIOLoop = MessageLoopForIO::current(); michael@0: michael@0: // Set up a read watch michael@0: mIOLoop->WatchFileDescriptor(mFd.get(), michael@0: true, michael@0: MessageLoopForIO::WATCH_READ, michael@0: &mReadWatcher, michael@0: this); michael@0: // Set up a write watch michael@0: mIOLoop->WatchFileDescriptor(mFd.get(), michael@0: false, michael@0: MessageLoopForIO::WATCH_WRITE, michael@0: &mWriteWatcher, michael@0: this); michael@0: } michael@0: michael@0: void michael@0: DroidSocketImpl::Listen() michael@0: { michael@0: MOZ_ASSERT(sBluetoothSocketInterface); michael@0: michael@0: // TODO: uuid and service name as arguments michael@0: michael@0: int fd = -1; michael@0: bt_status_t status = michael@0: sBluetoothSocketInterface->listen(BTSOCK_RFCOMM, michael@0: "OBEX Object Push", michael@0: UUID_OBEX_OBJECT_PUSH, michael@0: mChannel, michael@0: &fd, michael@0: (BTSOCK_FLAG_ENCRYPT * mEncrypt) | michael@0: (BTSOCK_FLAG_AUTH * mAuth)); michael@0: NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS); michael@0: NS_ENSURE_TRUE_VOID(fd >= 0); michael@0: michael@0: mFd = fd; michael@0: michael@0: MOZ_ASSERT(!mIOLoop); michael@0: mIOLoop = MessageLoopForIO::current(); michael@0: michael@0: // Set up a read watch michael@0: mIOLoop->WatchFileDescriptor(mFd.get(), michael@0: true, michael@0: MessageLoopForIO::WATCH_READ, michael@0: &mReadWatcher, michael@0: this); michael@0: } michael@0: michael@0: ssize_t michael@0: DroidSocketImpl::ReadMsg(int aFd, void *aBuffer, size_t aLength) michael@0: { michael@0: ssize_t ret; michael@0: struct msghdr msg; michael@0: struct iovec iv; michael@0: struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100]; michael@0: michael@0: memset(&msg, 0, sizeof(msg)); michael@0: memset(&iv, 0, sizeof(iv)); michael@0: michael@0: iv.iov_base = (unsigned char *)aBuffer; michael@0: iv.iov_len = aLength; michael@0: michael@0: msg.msg_iov = &iv; michael@0: msg.msg_iovlen = 1; michael@0: msg.msg_control = cmsgbuf; michael@0: msg.msg_controllen = sizeof(cmsgbuf); michael@0: michael@0: ret = recvmsg(mFd.get(), &msg, MSG_NOSIGNAL); michael@0: if (ret < 0 && errno == EPIPE) { michael@0: // Treat this as an end of stream michael@0: return 0; michael@0: } michael@0: michael@0: NS_ENSURE_FALSE(ret < 0, -1); michael@0: NS_ENSURE_FALSE(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE), -1); michael@0: michael@0: // Extract client fd from message header michael@0: for (struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg); michael@0: cmsgptr != nullptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { michael@0: if (cmsgptr->cmsg_level != SOL_SOCKET) { michael@0: continue; michael@0: } michael@0: if (cmsgptr->cmsg_type == SCM_RIGHTS) { michael@0: int *pDescriptors = (int *)CMSG_DATA(cmsgptr); michael@0: // Overwrite fd with client fd michael@0: mFd.reset(pDescriptors[0]); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: void michael@0: DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mShuttingDownOnIOThread); michael@0: michael@0: // Read all of the incoming data. michael@0: while (true) { michael@0: nsAutoPtr incoming(new UnixSocketRawData(MAX_READ_SIZE)); michael@0: michael@0: ssize_t ret; michael@0: if (!mReadMsgForClientFd) { michael@0: ret = read(aFd, incoming->mData, incoming->mSize); michael@0: } else { michael@0: ret = ReadMsg(aFd, incoming->mData, incoming->mSize); michael@0: } michael@0: michael@0: if (ret <= 0) { michael@0: if (ret == -1) { michael@0: if (errno == EINTR) { michael@0: continue; // retry system call when interrupted michael@0: } michael@0: if (errno == EAGAIN || errno == EWOULDBLOCK) { michael@0: return; // no data available: return and re-poll michael@0: } michael@0: michael@0: BT_WARNING("Cannot read from network"); michael@0: // else fall through to error handling on other errno's michael@0: } michael@0: michael@0: // We're done with our descriptors. Ensure that spurious events don't michael@0: // cause us to end up back here. michael@0: mReadWatcher.StopWatchingFileDescriptor(); michael@0: mWriteWatcher.StopWatchingFileDescriptor(); michael@0: nsRefPtr t = new RequestClosingSocketTask(this); michael@0: NS_DispatchToMainThread(t); michael@0: return; michael@0: } michael@0: michael@0: incoming->mSize = ret; michael@0: nsRefPtr t = michael@0: new SocketReceiveTask(this, incoming.forget()); michael@0: NS_DispatchToMainThread(t); michael@0: michael@0: // If ret is less than MAX_READ_SIZE, there's no michael@0: // more data in the socket for us to read now. michael@0: if (ret < ssize_t(MAX_READ_SIZE)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: MOZ_CRASH("We returned early"); michael@0: } michael@0: michael@0: void michael@0: DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mShuttingDownOnIOThread); michael@0: MOZ_ASSERT(aFd >= 0); michael@0: michael@0: // Try to write the bytes of mCurrentRilRawData. If all were written, continue. michael@0: // michael@0: // Otherwise, save the byte position of the next byte to write michael@0: // within mCurrentWriteOffset, and request another write when the michael@0: // system won't block. michael@0: // michael@0: while (true) { michael@0: UnixSocketRawData* data; michael@0: if (mOutgoingQ.IsEmpty()) { michael@0: return; michael@0: } michael@0: data = mOutgoingQ.ElementAt(0); michael@0: const uint8_t *toWrite; michael@0: toWrite = data->mData; michael@0: michael@0: while (data->mCurrentWriteOffset < data->mSize) { michael@0: ssize_t write_amount = data->mSize - data->mCurrentWriteOffset; michael@0: ssize_t written; michael@0: written = write (aFd, toWrite + data->mCurrentWriteOffset, michael@0: write_amount); michael@0: if (written > 0) { michael@0: data->mCurrentWriteOffset += written; michael@0: } michael@0: if (written != write_amount) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (data->mCurrentWriteOffset != data->mSize) { michael@0: MessageLoopForIO::current()->WatchFileDescriptor( michael@0: aFd, michael@0: false, michael@0: MessageLoopForIO::WATCH_WRITE, michael@0: &mWriteWatcher, michael@0: this); michael@0: return; michael@0: } michael@0: mOutgoingQ.RemoveElementAt(0); michael@0: delete data; michael@0: } michael@0: } michael@0: michael@0: BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver, michael@0: BluetoothSocketType aType, michael@0: bool aAuth, michael@0: bool aEncrypt) michael@0: : mObserver(aObserver) michael@0: , mImpl(nullptr) michael@0: , mAuth(aAuth) michael@0: , mEncrypt(aEncrypt) michael@0: , mReceivedSocketInfoLength(0) michael@0: { michael@0: MOZ_ASSERT(aObserver); michael@0: michael@0: EnsureBluetoothSocketHalLoad(); michael@0: mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); michael@0: } michael@0: michael@0: void michael@0: BluetoothSocket::CloseDroidSocket() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!mImpl) { michael@0: return; michael@0: } michael@0: michael@0: // From this point on, we consider mImpl as being deleted. michael@0: // We sever the relationship here so any future calls to listen or connect michael@0: // will create a new implementation. michael@0: mImpl->ShutdownOnMainThread(); michael@0: XRE_GetIOMessageLoop()->PostTask(FROM_HERE, michael@0: new ShutdownSocketTask(mImpl)); michael@0: mImpl = nullptr; michael@0: michael@0: OnDisconnect(); michael@0: } michael@0: michael@0: bool michael@0: BluetoothSocket::Connect(const nsAString& aDeviceAddress, int aChannel) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: NS_ENSURE_FALSE(mImpl, false); michael@0: michael@0: mIsServer = false; michael@0: mImpl = new DroidSocketImpl(this, aDeviceAddress, aChannel, mAuth, mEncrypt); michael@0: XRE_GetIOMessageLoop()->PostTask(FROM_HERE, michael@0: new SocketConnectTask(mImpl)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothSocket::Listen(int aChannel) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: NS_ENSURE_FALSE(mImpl, false); michael@0: michael@0: mIsServer = true; michael@0: mImpl = new DroidSocketImpl(this, aChannel, mAuth, mEncrypt); michael@0: XRE_GetIOMessageLoop()->PostTask(FROM_HERE, michael@0: new SocketListenTask(mImpl)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: NS_ENSURE_TRUE(mImpl, false); michael@0: michael@0: MOZ_ASSERT(!mImpl->IsShutdownOnMainThread()); michael@0: XRE_GetIOMessageLoop()->PostTask(FROM_HERE, michael@0: new SocketSendTask(this, mImpl, aData)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothSocket::ReceiveSocketInfo(nsAutoPtr& aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: /** michael@0: * 2 socket info messages (20 bytes) to receive at the beginning: michael@0: * - 1st message: [channel:4] michael@0: * - 2nd message: [size:2][bd address:6][channel:4][connection status:4] michael@0: */ michael@0: if (mReceivedSocketInfoLength >= TOTAL_SOCKET_INFO_LENGTH) { michael@0: // We've got both socket info messages michael@0: return false; michael@0: } michael@0: mReceivedSocketInfoLength += aMessage->mSize; michael@0: michael@0: size_t offset = 0; michael@0: if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) { michael@0: // 1st message: [channel:4] michael@0: int32_t channel = ReadInt32(aMessage->mData, &offset); michael@0: BT_LOGR("channel %d", channel); michael@0: michael@0: // If this is server socket, read header of next message for client fd michael@0: mImpl->mReadMsgForClientFd = mIsServer; michael@0: } else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) { michael@0: // 2nd message: [size:2][bd address:6][channel:4][connection status:4] michael@0: int16_t size = ReadInt16(aMessage->mData, &offset); michael@0: ReadBdAddress(aMessage->mData, &offset, mDeviceAddress); michael@0: int32_t channel = ReadInt32(aMessage->mData, &offset); michael@0: int32_t connectionStatus = ReadInt32(aMessage->mData, &offset); michael@0: michael@0: BT_LOGR("size %d channel %d remote addr %s status %d", michael@0: size, channel, NS_ConvertUTF16toUTF8(mDeviceAddress).get(), connectionStatus); michael@0: michael@0: if (connectionStatus != 0) { michael@0: OnConnectError(); michael@0: return true; michael@0: } michael@0: michael@0: if (mIsServer) { michael@0: mImpl->mReadMsgForClientFd = false; michael@0: // Connect client fd on IO thread michael@0: XRE_GetIOMessageLoop()->PostTask(FROM_HERE, michael@0: new SocketConnectClientFdTask(mImpl)); michael@0: } michael@0: OnConnectSuccess(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothSocket::ReceiveSocketData(nsAutoPtr& aMessage) michael@0: { michael@0: if (ReceiveSocketInfo(aMessage)) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mObserver); michael@0: mObserver->ReceiveSocketData(this, aMessage); michael@0: } michael@0: michael@0: void michael@0: BluetoothSocket::OnConnectSuccess() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mObserver); michael@0: mObserver->OnSocketConnectSuccess(this); michael@0: } michael@0: michael@0: void michael@0: BluetoothSocket::OnConnectError() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mObserver); michael@0: mObserver->OnSocketConnectError(this); michael@0: } michael@0: michael@0: void michael@0: BluetoothSocket::OnDisconnect() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mObserver); michael@0: mObserver->OnSocketDisconnect(this); michael@0: } michael@0: