/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ipc/Ril.h" #include <fcntl.h> #include <sys/socket.h> #include <sys/un.h> #include "jsfriendapi.h" #include "mozilla/ArrayUtils.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/workers/Workers.h" #include "mozilla/ipc/RilSocket.h" #include "mozilla/ipc/RilSocketConsumer.h" #include "nsThreadUtils.h" // For NS_IsMainThread. #include "RilConnector.h" #ifdef CHROMIUM_LOG #undef CHROMIUM_LOG #endif #define CHROMIUM_LOG(args...) printf(args); namespace mozilla { namespace ipc { USING_WORKERS_NAMESPACE; using namespace JS; class RilConsumer; static const char RIL_SOCKET_NAME[] = "/dev/socket/rilproxy"; static nsTArray<UniquePtr<RilConsumer>> sRilConsumers; // // RilConsumer // class RilConsumer final : public RilSocketConsumer { public: RilConsumer(); nsresult ConnectWorkerToRIL(JSContext* aCx); nsresult Register(unsigned long aClientId, WorkerCrossThreadDispatcher* aDispatcher); void Unregister(); // Methods for |RilSocketConsumer| // void ReceiveSocketData(JSContext* aCx, int aIndex, UniquePtr<UnixSocketBuffer>& aBuffer) override; void OnConnectSuccess(int aIndex) override; void OnConnectError(int aIndex) override; void OnDisconnect(int aIndex) override; protected: static bool PostRILMessage(JSContext* aCx, unsigned aArgc, Value* aVp); nsresult Send(JSContext* aCx, const CallArgs& aArgs); nsresult Receive(JSContext* aCx, uint32_t aClientId, const UnixSocketBuffer* aBuffer); void Close(); private: RefPtr<RilSocket> mSocket; nsCString mAddress; bool mShutdown; }; RilConsumer::RilConsumer() : mShutdown(false) { } nsresult RilConsumer::ConnectWorkerToRIL(JSContext* aCx) { // Set up the postRILMessage on the function for worker -> RIL thread // communication. Rooted<JSObject*> workerGlobal(aCx, CurrentGlobalOrNull(aCx)); // Check whether |postRILMessage| has been defined. No one but this class // should ever define |postRILMessage| in a RIL worker. Rooted<Value> val(aCx); if (!JS_GetProperty(aCx, workerGlobal, "postRILMessage", &val)) { // Just returning failure here will cause the exception on the JSContext to // be reported as needed. return NS_ERROR_FAILURE; } // Make sure that |postRILMessage| is a function. if (JSTYPE_FUNCTION == JS_TypeOfValue(aCx, val)) { return NS_OK; } JSFunction* postRILMessage = JS_DefineFunction(aCx, workerGlobal, "postRILMessage", PostRILMessage, 2, 0); if (NS_WARN_IF(!postRILMessage)) { // Just returning failure here will cause the exception on the JSContext to // be reported as needed. return NS_ERROR_FAILURE; } return NS_OK; } nsresult RilConsumer::Register(unsigned long aClientId, WorkerCrossThreadDispatcher* aDispatcher) { // Only append client id after RIL_SOCKET_NAME when it's not connected to // the first(0) rilproxy for compatibility. if (!aClientId) { mAddress = RIL_SOCKET_NAME; } else { struct sockaddr_un addr_un; snprintf(addr_un.sun_path, sizeof addr_un.sun_path, "%s%lu", RIL_SOCKET_NAME, aClientId); mAddress = addr_un.sun_path; } mSocket = new RilSocket(aDispatcher, this, aClientId); nsresult rv = mSocket->Connect(new RilConnector(mAddress, aClientId)); if (NS_FAILED(rv)) { return rv; } return NS_OK; } void RilConsumer::Unregister() { mShutdown = true; Close(); } bool RilConsumer::PostRILMessage(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); if (args.length() != 2) { JS_ReportErrorASCII(aCx, "Expecting two arguments with the RIL message"); return false; } int clientId = args[0].toInt32(); if ((ssize_t)sRilConsumers.Length() <= clientId || !sRilConsumers[clientId]) { // Probably shutting down. return true; } nsresult rv = sRilConsumers[clientId]->Send(aCx, args); if (NS_FAILED(rv)) { return false; } return true; } nsresult RilConsumer::Send(JSContext* aCx, const CallArgs& aArgs) { if (NS_WARN_IF(!mSocket) || NS_WARN_IF(mSocket->GetConnectionStatus() == SOCKET_DISCONNECTED)) { // Probably shutting down. return NS_OK; } UniquePtr<UnixSocketRawData> raw; Value v = aArgs[1]; if (v.isString()) { JSAutoByteString abs; Rooted<JSString*> str(aCx, v.toString()); if (!abs.encodeUtf8(aCx, str)) { return NS_ERROR_FAILURE; } raw = MakeUnique<UnixSocketRawData>(abs.ptr(), abs.length()); } else if (!v.isPrimitive()) { JSObject* obj = v.toObjectOrNull(); if (!JS_IsTypedArrayObject(obj)) { JS_ReportErrorASCII(aCx, "Object passed in wasn't a typed array"); return NS_ERROR_FAILURE; } uint32_t type = JS_GetArrayBufferViewType(obj); if (type != js::Scalar::Int8 && type != js::Scalar::Uint8 && type != js::Scalar::Uint8Clamped) { JS_ReportErrorASCII(aCx, "Typed array data is not octets"); return NS_ERROR_FAILURE; } size_t size = JS_GetTypedArrayByteLength(obj); bool isShared; void* data; { AutoCheckCannotGC nogc; data = JS_GetArrayBufferViewData(obj, &isShared, nogc); } if (isShared) { JS_ReportErrorASCII( aCx, "Incorrect argument. Shared memory not supported"); return NS_ERROR_FAILURE; } raw = MakeUnique<UnixSocketRawData>(data, size); } else { JS_ReportErrorASCII( aCx, "Incorrect argument. Expecting a string or a typed array"); return NS_ERROR_FAILURE; } if (!raw) { JS_ReportErrorASCII(aCx, "Unable to post to RIL"); return NS_ERROR_FAILURE; } mSocket->SendSocketData(raw.release()); return NS_OK; } nsresult RilConsumer::Receive(JSContext* aCx, uint32_t aClientId, const UnixSocketBuffer* aBuffer) { MOZ_ASSERT(aBuffer); Rooted<JSObject*> obj(aCx, CurrentGlobalOrNull(aCx)); Rooted<JSObject*> array(aCx, JS_NewUint8Array(aCx, aBuffer->GetSize())); if (NS_WARN_IF(!array)) { // Just suppress the exception, since our callers don't have a way to // indicate they failed. JS_ClearPendingException(aCx); return NS_ERROR_FAILURE; } { AutoCheckCannotGC nogc; bool isShared; memcpy(JS_GetArrayBufferViewData(array, &isShared, nogc), aBuffer->GetData(), aBuffer->GetSize()); MOZ_ASSERT(!isShared); // Array was constructed above. } AutoValueArray<2> args(aCx); args[0].setNumber(aClientId); args[1].setObject(*array); Rooted<Value> rval(aCx); JS_CallFunctionName(aCx, obj, "onRILMessage", args, &rval); // Just suppress the exception, since our callers don't have a way to // indicate they failed. JS_ClearPendingException(aCx); return NS_OK; } void RilConsumer::Close() { if (mSocket) { mSocket->Close(); mSocket = nullptr; } } // |RilSocketConnector| void RilConsumer::ReceiveSocketData(JSContext* aCx, int aIndex, UniquePtr<UnixSocketBuffer>& aBuffer) { Receive(aCx, (uint32_t)aIndex, aBuffer.get()); } void RilConsumer::OnConnectSuccess(int aIndex) { // Nothing to do here. CHROMIUM_LOG("RIL[%d]: %s\n", aIndex, __FUNCTION__); } void RilConsumer::OnConnectError(int aIndex) { CHROMIUM_LOG("RIL[%d]: %s\n", aIndex, __FUNCTION__); Close(); } void RilConsumer::OnDisconnect(int aIndex) { CHROMIUM_LOG("RIL[%d]: %s\n", aIndex, __FUNCTION__); if (mShutdown) { return; } mSocket->Connect(new RilConnector(mAddress, aIndex), mSocket->GetSuggestedConnectDelayMs()); } // // RilWorker // nsTArray<UniquePtr<RilWorker>> RilWorker::sRilWorkers; nsresult RilWorker::Register(unsigned int aClientId, WorkerCrossThreadDispatcher* aDispatcher) { MOZ_ASSERT(NS_IsMainThread()); sRilWorkers.EnsureLengthAtLeast(aClientId + 1); if (sRilWorkers[aClientId]) { NS_WARNING("RilWorkers already registered"); return NS_ERROR_FAILURE; } // Now that we're set up, connect ourselves to the RIL thread. sRilWorkers[aClientId] = MakeUnique<RilWorker>(aDispatcher); nsresult rv = sRilWorkers[aClientId]->RegisterConsumer(aClientId); if (NS_FAILED(rv)) { return rv; } return NS_OK; } void RilWorker::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); for (size_t i = 0; i < sRilWorkers.Length(); ++i) { if (!sRilWorkers[i]) { continue; } sRilWorkers[i]->UnregisterConsumer(i); sRilWorkers[i] = nullptr; } } RilWorker::RilWorker(WorkerCrossThreadDispatcher* aDispatcher) : mDispatcher(aDispatcher) { MOZ_ASSERT(mDispatcher); } class RilWorker::RegisterConsumerTask : public WorkerTask { public: RegisterConsumerTask(unsigned int aClientId, WorkerCrossThreadDispatcher* aDispatcher) : mClientId(aClientId) , mDispatcher(aDispatcher) { MOZ_ASSERT(mDispatcher); } bool RunTask(JSContext* aCx) override { sRilConsumers.EnsureLengthAtLeast(mClientId + 1); MOZ_ASSERT(!sRilConsumers[mClientId]); auto rilConsumer = MakeUnique<RilConsumer>(); nsresult rv = rilConsumer->ConnectWorkerToRIL(aCx); if (NS_FAILED(rv)) { return false; } rv = rilConsumer->Register(mClientId, mDispatcher); if (NS_FAILED(rv)) { return false; } sRilConsumers[mClientId] = Move(rilConsumer); return true; } private: unsigned int mClientId; RefPtr<WorkerCrossThreadDispatcher> mDispatcher; }; nsresult RilWorker::RegisterConsumer(unsigned int aClientId) { RefPtr<RegisterConsumerTask> task = new RegisterConsumerTask(aClientId, mDispatcher); if (!mDispatcher->PostTask(task)) { NS_WARNING("Failed to post register-consumer task."); return NS_ERROR_UNEXPECTED; } return NS_OK; } class RilWorker::UnregisterConsumerTask : public WorkerTask { public: UnregisterConsumerTask(unsigned int aClientId) : mClientId(aClientId) { } bool RunTask(JSContext* aCx) override { MOZ_ASSERT(mClientId < sRilConsumers.Length()); MOZ_ASSERT(sRilConsumers[mClientId]); sRilConsumers[mClientId]->Unregister(); sRilConsumers[mClientId] = nullptr; return true; } private: unsigned int mClientId; }; void RilWorker::UnregisterConsumer(unsigned int aClientId) { RefPtr<UnregisterConsumerTask> task = new UnregisterConsumerTask(aClientId); if (!mDispatcher->PostTask(task)) { NS_WARNING("Failed to post unregister-consumer task."); return; } } } // namespace ipc } // namespace mozilla