summaryrefslogtreecommitdiffstats
path: root/hal/gonk/GonkSensorsInterface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'hal/gonk/GonkSensorsInterface.cpp')
-rw-r--r--hal/gonk/GonkSensorsInterface.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/hal/gonk/GonkSensorsInterface.cpp b/hal/gonk/GonkSensorsInterface.cpp
new file mode 100644
index 000000000..51e1ff50c
--- /dev/null
+++ b/hal/gonk/GonkSensorsInterface.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GonkSensorsInterface.h"
+#include "GonkSensorsPollInterface.h"
+#include "GonkSensorsRegistryInterface.h"
+#include "HalLog.h"
+#include <mozilla/ipc/DaemonSocket.h>
+#include <mozilla/ipc/DaemonSocketConnector.h>
+#include <mozilla/ipc/ListenSocket.h>
+
+namespace mozilla {
+namespace hal {
+
+using namespace mozilla::ipc;
+
+//
+// GonkSensorsResultHandler
+//
+
+void
+GonkSensorsResultHandler::OnError(SensorsError aError)
+{
+ HAL_ERR("Received error code %d", static_cast<int>(aError));
+}
+
+void
+GonkSensorsResultHandler::Connect()
+{ }
+
+void
+GonkSensorsResultHandler::Disconnect()
+{ }
+
+GonkSensorsResultHandler::~GonkSensorsResultHandler()
+{ }
+
+//
+// GonkSensorsNotificationHandler
+//
+
+void
+GonkSensorsNotificationHandler::BackendErrorNotification(bool aCrashed)
+{
+ if (aCrashed) {
+ HAL_ERR("Sensors backend crashed");
+ } else {
+ HAL_ERR("Error in sensors backend");
+ }
+}
+
+GonkSensorsNotificationHandler::~GonkSensorsNotificationHandler()
+{ }
+
+//
+// GonkSensorsProtocol
+//
+
+class GonkSensorsProtocol final
+ : public DaemonSocketIOConsumer
+ , public GonkSensorsRegistryModule
+ , public GonkSensorsPollModule
+{
+public:
+ GonkSensorsProtocol();
+
+ void SetConnection(DaemonSocket* aConnection);
+
+ already_AddRefed<DaemonSocketResultHandler> FetchResultHandler(
+ const DaemonSocketPDUHeader& aHeader);
+
+ // Methods for |SensorsRegistryModule| and |SensorsPollModule|
+ //
+
+ nsresult Send(DaemonSocketPDU* aPDU,
+ DaemonSocketResultHandler* aRes) override;
+
+ // Methods for |DaemonSocketIOConsumer|
+ //
+
+ void Handle(DaemonSocketPDU& aPDU) override;
+ void StoreResultHandler(const DaemonSocketPDU& aPDU) override;
+
+private:
+ void HandleRegistrySvc(const DaemonSocketPDUHeader& aHeader,
+ DaemonSocketPDU& aPDU,
+ DaemonSocketResultHandler* aRes);
+ void HandlePollSvc(const DaemonSocketPDUHeader& aHeader,
+ DaemonSocketPDU& aPDU,
+ DaemonSocketResultHandler* aRes);
+
+ DaemonSocket* mConnection;
+ nsTArray<RefPtr<DaemonSocketResultHandler>> mResultHandlerQ;
+};
+
+GonkSensorsProtocol::GonkSensorsProtocol()
+{ }
+
+void
+GonkSensorsProtocol::SetConnection(DaemonSocket* aConnection)
+{
+ mConnection = aConnection;
+}
+
+already_AddRefed<DaemonSocketResultHandler>
+GonkSensorsProtocol::FetchResultHandler(const DaemonSocketPDUHeader& aHeader)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (aHeader.mOpcode & 0x80) {
+ return nullptr; // Ignore notifications
+ }
+
+ RefPtr<DaemonSocketResultHandler> res = mResultHandlerQ.ElementAt(0);
+ mResultHandlerQ.RemoveElementAt(0);
+
+ return res.forget();
+}
+
+void
+GonkSensorsProtocol::HandleRegistrySvc(
+ const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU,
+ DaemonSocketResultHandler* aRes)
+{
+ GonkSensorsRegistryModule::HandleSvc(aHeader, aPDU, aRes);
+}
+
+void
+GonkSensorsProtocol::HandlePollSvc(
+ const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU,
+ DaemonSocketResultHandler* aRes)
+{
+ GonkSensorsPollModule::HandleSvc(aHeader, aPDU, aRes);
+}
+
+// |SensorsRegistryModule|, |SensorsPollModule|
+
+nsresult
+GonkSensorsProtocol::Send(DaemonSocketPDU* aPDU,
+ DaemonSocketResultHandler* aRes)
+{
+ MOZ_ASSERT(mConnection);
+ MOZ_ASSERT(aPDU);
+
+ aPDU->SetConsumer(this);
+ aPDU->SetResultHandler(aRes);
+ aPDU->UpdateHeader();
+
+ if (mConnection->GetConnectionStatus() == SOCKET_DISCONNECTED) {
+ HAL_ERR("Sensors socket is disconnected");
+ return NS_ERROR_FAILURE;
+ }
+
+ mConnection->SendSocketData(aPDU); // Forward PDU to data channel
+
+ return NS_OK;
+}
+
+// |DaemonSocketIOConsumer|
+
+void
+GonkSensorsProtocol::Handle(DaemonSocketPDU& aPDU)
+{
+ static void (GonkSensorsProtocol::* const HandleSvc[])(
+ const DaemonSocketPDUHeader&, DaemonSocketPDU&,
+ DaemonSocketResultHandler*) = {
+ [GonkSensorsRegistryModule::SERVICE_ID] =
+ &GonkSensorsProtocol::HandleRegistrySvc,
+ [GonkSensorsPollModule::SERVICE_ID] =
+ &GonkSensorsProtocol::HandlePollSvc
+ };
+
+ DaemonSocketPDUHeader header;
+
+ if (NS_FAILED(UnpackPDU(aPDU, header))) {
+ return;
+ }
+ if (!(header.mService < MOZ_ARRAY_LENGTH(HandleSvc)) ||
+ !HandleSvc[header.mService]) {
+ HAL_ERR("Sensors service %d unknown", header.mService);
+ return;
+ }
+
+ RefPtr<DaemonSocketResultHandler> res = FetchResultHandler(header);
+
+ (this->*(HandleSvc[header.mService]))(header, aPDU, res);
+}
+
+void
+GonkSensorsProtocol::StoreResultHandler(const DaemonSocketPDU& aPDU)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mResultHandlerQ.AppendElement(aPDU.GetResultHandler());
+}
+
+//
+// GonkSensorsInterface
+//
+
+GonkSensorsInterface*
+GonkSensorsInterface::GetInstance()
+{
+ static GonkSensorsInterface* sGonkSensorsInterface;
+
+ if (sGonkSensorsInterface) {
+ return sGonkSensorsInterface;
+ }
+
+ sGonkSensorsInterface = new GonkSensorsInterface();
+
+ return sGonkSensorsInterface;
+}
+
+void
+GonkSensorsInterface::SetNotificationHandler(
+ GonkSensorsNotificationHandler* aNotificationHandler)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mNotificationHandler = aNotificationHandler;
+}
+
+/*
+ * The connect procedure consists of several steps.
+ *
+ * (1) Start listening for the command channel's socket connection: We
+ * do this before anything else, so that we don't miss connection
+ * requests from the Sensors daemon. This step will create a listen
+ * socket.
+ *
+ * (2) Start the Sensors daemon: When the daemon starts up it will open
+ * a socket connection to Gecko and thus create the data channel.
+ * Gecko already opened the listen socket in step (1). Step (2) ends
+ * with the creation of the data channel.
+ *
+ * (3) Signal success to the caller.
+ *
+ * If any step fails, we roll-back the procedure and signal an error to the
+ * caller.
+ */
+void
+GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandler,
+ GonkSensorsResultHandler* aRes)
+{
+#define BASE_SOCKET_NAME "sensorsd"
+ static unsigned long POSTFIX_LENGTH = 16;
+
+ // If we could not cleanup properly before and an old
+ // instance of the daemon is still running, we kill it
+ // here.
+ mozilla::hal::StopSystemService("sensorsd");
+
+ mNotificationHandler = aNotificationHandler;
+
+ mResultHandlerQ.AppendElement(aRes);
+
+ if (!mProtocol) {
+ mProtocol = MakeUnique<GonkSensorsProtocol>();
+ }
+
+ if (!mListenSocket) {
+ mListenSocket = new ListenSocket(this, LISTEN_SOCKET);
+ }
+
+ // Init, step 1: Listen for data channel... */
+
+ if (!mDataSocket) {
+ mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET);
+ } else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) {
+ // Command channel should not be open; let's close it.
+ mDataSocket->Close();
+ }
+
+ // The listen socket's name is generated with a random postfix. This
+ // avoids naming collisions if we still have a listen socket from a
+ // previously failed cleanup. It also makes it hard for malicious
+ // external programs to capture the socket name or connect before
+ // the daemon can do so. If no random postfix can be generated, we
+ // simply use the base name as-is.
+ nsresult rv = DaemonSocketConnector::CreateRandomAddressString(
+ NS_LITERAL_CSTRING(BASE_SOCKET_NAME), POSTFIX_LENGTH, mListenSocketName);
+ if (NS_FAILED(rv)) {
+ mListenSocketName.AssignLiteral(BASE_SOCKET_NAME);
+ }
+
+ rv = mListenSocket->Listen(new DaemonSocketConnector(mListenSocketName),
+ mDataSocket);
+ if (NS_FAILED(rv)) {
+ OnConnectError(DATA_SOCKET);
+ return;
+ }
+
+ // The protocol implementation needs a data channel for
+ // sending commands to the daemon. We set it here, because
+ // this is the earliest time when it's available.
+ mProtocol->SetConnection(mDataSocket);
+}
+
+/*
+ * Disconnecting is inverse to connecting.
+ *
+ * (1) Close data socket: We close the data channel and the daemon will
+ * will notice. Once we see the socket's disconnect, we continue with
+ * the cleanup.
+ *
+ * (2) Close listen socket: The listen socket is not active any longer
+ * and we simply close it.
+ *
+ * (3) Signal success to the caller.
+ *
+ * We don't have to stop the daemon explicitly. It will cleanup and quit
+ * after it noticed the closing of the data channel
+ *
+ * Rolling back half-completed cleanups is not possible. In the case of
+ * an error, we simply push forward and try to recover during the next
+ * initialization.
+ */
+void
+GonkSensorsInterface::Disconnect(GonkSensorsResultHandler* aRes)
+{
+ mNotificationHandler = nullptr;
+
+ // Cleanup, step 1: Close data channel
+ mDataSocket->Close();
+
+ mResultHandlerQ.AppendElement(aRes);
+}
+
+GonkSensorsRegistryInterface*
+GonkSensorsInterface::GetSensorsRegistryInterface()
+{
+ if (mRegistryInterface) {
+ return mRegistryInterface.get();
+ }
+
+ mRegistryInterface = MakeUnique<GonkSensorsRegistryInterface>(mProtocol.get());
+
+ return mRegistryInterface.get();
+}
+
+GonkSensorsPollInterface*
+GonkSensorsInterface::GetSensorsPollInterface()
+{
+ if (mPollInterface) {
+ return mPollInterface.get();
+ }
+
+ mPollInterface = MakeUnique<GonkSensorsPollInterface>(mProtocol.get());
+
+ return mPollInterface.get();
+}
+
+GonkSensorsInterface::GonkSensorsInterface()
+ : mNotificationHandler(nullptr)
+{ }
+
+GonkSensorsInterface::~GonkSensorsInterface()
+{ }
+
+void
+GonkSensorsInterface::DispatchError(GonkSensorsResultHandler* aRes,
+ SensorsError aError)
+{
+ DaemonResultRunnable1<GonkSensorsResultHandler, void,
+ SensorsError, SensorsError>::Dispatch(
+ aRes, &GonkSensorsResultHandler::OnError,
+ ConstantInitOp1<SensorsError>(aError));
+}
+
+void
+GonkSensorsInterface::DispatchError(
+ GonkSensorsResultHandler* aRes, nsresult aRv)
+{
+ SensorsError error;
+
+ if (NS_FAILED(Convert(aRv, error))) {
+ error = SENSORS_ERROR_FAIL;
+ }
+ DispatchError(aRes, error);
+}
+
+// |DaemonSocketConsumer|, |ListenSocketConsumer|
+
+void
+GonkSensorsInterface::OnConnectSuccess(int aIndex)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResultHandlerQ.IsEmpty());
+
+ switch (aIndex) {
+ case LISTEN_SOCKET: {
+ // Init, step 2: Start Sensors daemon
+ nsCString args("-a ");
+ args.Append(mListenSocketName);
+ mozilla::hal::StartSystemService("sensorsd", args.get());
+ }
+ break;
+ case DATA_SOCKET:
+ if (!mResultHandlerQ.IsEmpty()) {
+ // Init, step 3: Signal success
+ RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
+ mResultHandlerQ.RemoveElementAt(0);
+ if (res) {
+ res->Connect();
+ }
+ }
+ break;
+ }
+}
+
+void
+GonkSensorsInterface::OnConnectError(int aIndex)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResultHandlerQ.IsEmpty());
+
+ switch (aIndex) {
+ case DATA_SOCKET:
+ // Stop daemon and close listen socket
+ mozilla::hal::StopSystemService("sensorsd");
+ mListenSocket->Close();
+ // fall through
+ case LISTEN_SOCKET:
+ if (!mResultHandlerQ.IsEmpty()) {
+ // Signal error to caller
+ RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
+ mResultHandlerQ.RemoveElementAt(0);
+ if (res) {
+ DispatchError(res, SENSORS_ERROR_FAIL);
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * Disconnects can happend
+ *
+ * (a) during startup,
+ * (b) during regular service, or
+ * (c) during shutdown.
+ *
+ * For cases (a) and (c), |mResultHandlerQ| contains an element. For
+ * case (b) |mResultHandlerQ| will be empty. This distinguishes a crash in
+ * the daemon. The following procedure to recover from crashes consists of
+ * several steps for case (b).
+ *
+ * (1) Close listen socket.
+ * (2) Wait for all sockets to be disconnected and inform caller about
+ * the crash.
+ * (3) After all resources have been cleaned up, let the caller restart
+ * the daemon.
+ */
+void
+GonkSensorsInterface::OnDisconnect(int aIndex)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ switch (aIndex) {
+ case DATA_SOCKET:
+ // Cleanup, step 2 (Recovery, step 1): Close listen socket
+ mListenSocket->Close();
+ break;
+ case LISTEN_SOCKET:
+ // Cleanup, step 3: Signal success to caller
+ if (!mResultHandlerQ.IsEmpty()) {
+ RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
+ mResultHandlerQ.RemoveElementAt(0);
+ if (res) {
+ res->Disconnect();
+ }
+ }
+ break;
+ }
+
+ /* For recovery make sure all sockets disconnected, in order to avoid
+ * the remaining disconnects interfere with the restart procedure.
+ */
+ if (mNotificationHandler && mResultHandlerQ.IsEmpty()) {
+ if (mListenSocket->GetConnectionStatus() == SOCKET_DISCONNECTED &&
+ mDataSocket->GetConnectionStatus() == SOCKET_DISCONNECTED) {
+ // Recovery, step 2: Notify the caller to prepare the restart procedure.
+ mNotificationHandler->BackendErrorNotification(true);
+ mNotificationHandler = nullptr;
+ }
+ }
+}
+
+} // namespace hal
+} // namespace mozilla