summaryrefslogtreecommitdiff
path: root/src/leap/common/events
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-04-02 10:48:41 -0300
committerTomás Touceda <chiiph@leap.se>2013-04-02 10:48:41 -0300
commit138def37372c44815ebc6e8cdb117e1158044d8e (patch)
treee6085b25f0372ed20b0112420ddb0a26b5d34d92 /src/leap/common/events
parent20c2b8986df3451d9d1f9e45a266731805f564b6 (diff)
parent9b5a6961a5662edf11ecee36caa5accbbda0281d (diff)
Merge remote-tracking branch 'drebs/feature/events-signals' into develop
Conflicts: .gitignore src/leap/common/__init__.py
Diffstat (limited to 'src/leap/common/events')
-rw-r--r--src/leap/common/events/Makefile31
-rw-r--r--src/leap/common/events/README45
-rw-r--r--src/leap/common/events/__init__.py100
-rw-r--r--src/leap/common/events/component.py233
-rw-r--r--src/leap/common/events/daemon.py208
-rw-r--r--src/leap/common/events/events.proto69
-rw-r--r--src/leap/common/events/events_pb2.py364
-rw-r--r--src/leap/common/events/mac_auth.py31
-rw-r--r--src/leap/common/events/server.py150
9 files changed, 1231 insertions, 0 deletions
diff --git a/src/leap/common/events/Makefile b/src/leap/common/events/Makefile
new file mode 100644
index 0000000..4f73dea
--- /dev/null
+++ b/src/leap/common/events/Makefile
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Makefile
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This file is used to generate protobuf python files that are used for IPC:
+#
+# https://developers.google.com/protocol-buffers/docs/pythontutorial
+
+PROTOC = protoc
+
+all: events_pb2.py
+
+%_pb2.py: %.proto
+ $(PROTOC) --python_out=./ $<
+ autopep8 --in-place --aggressive $@
+
+clean:
+ rm -f *_pb2.py
diff --git a/src/leap/common/events/README b/src/leap/common/events/README
new file mode 100644
index 0000000..61b320d
--- /dev/null
+++ b/src/leap/common/events/README
@@ -0,0 +1,45 @@
+Events mechanism
+================
+
+The events mechanism allows for "components" to send signal events to each
+other by means of a centralized server. Components can register with the
+server to receive signals of certain types, and they can also send signals to
+the server that will then redistribute these signals to registered components.
+
+
+Listening daemons
+-----------------
+
+Both components and the server listen for incoming messages by using a
+listening daemon that runs in its own thread. The server daemon has to be
+started explicitly, while components daemon will be started whenever a
+component registers with the server to receive messages.
+
+
+How to use it
+-------------
+
+To start the events server:
+
+>>> from leap.common.events import server
+>>> server.ensure_server(port=8090)
+
+To register a callback to be called when a given signal is raised:
+
+>>> from leap.common.events import (
+>>> register,
+>>> events_pb2 as proto,
+>>> )
+>>>
+>>> def mycallback(sigreq):
+>>> print str(sigreq)
+>>>
+>>> events.register(signal=proto.CLIENT_UID, callback=mycallback)
+
+To signal an event:
+
+>>> from leap.common.events import (
+>>> signal,
+>>> events_pb2 as proto,
+>>> )
+>>> signal(proto.CLIENT_UID)
diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py
new file mode 100644
index 0000000..b8441bd
--- /dev/null
+++ b/src/leap/common/events/__init__.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# __init__.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+An events mechanism that allows for signaling of events between components.
+"""
+
+import logging
+import socket
+
+
+from leap.common.events import (
+ events_pb2,
+ server,
+ component,
+ daemon,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+def register(signal, callback, uid=None, replace=False, reqcbk=None,
+ timeout=1000):
+ """
+ Register a callback to be called when the given signal is received.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ @param signal: the signal that causes the callback to be launched
+ @type signal: int (see the `events.proto` file)
+ @param callback: the callback to be called when the signal is received
+ @type callback: function
+ @param uid: a unique id for the callback
+ @type uid: int
+ @param replace: should an existent callback with same uid be replaced?
+ @type replace: bool
+ @param reqcbk: a callback to be called when a response from server is
+ received
+ @type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ @param timeout: the timeout for synch calls
+ @type timeout: int
+
+ @return: the response from server for synch calls or nothing for asynch
+ calls
+ @rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ return component.register(signal, callback, uid, replace, reqcbk, timeout)
+
+
+def signal(signal, content="", mac_method="", mac="", reqcbk=None,
+ timeout=1000):
+ """
+ Send `signal` event to events server.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ @param signal: the signal that causes the callback to be launched
+ @type signal: int (see the `events.proto` file)
+ @param content: the contents of the event signal
+ @type content: str
+ @param mac_method: the method used to auth mac
+ @type mac_method: str
+ @param mac: the content of the auth mac
+ @type mac: str
+ @param reqcbk: a callback to be called when a response from server is
+ received
+ @type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ @param timeout: the timeout for synch calls
+ @type timeout: int
+
+ @return: the response from server for synch calls or nothing for asynch
+ calls
+ @rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ return component.signal(signal)
diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py
new file mode 100644
index 0000000..4fcd6e7
--- /dev/null
+++ b/src/leap/common/events/component.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+# component.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+The component end point of the events mechanism.
+
+Components are the communicating parties of the events mechanism. They
+communicate by sending messages to a server, which in turn redistributes
+messages to other components.
+
+When a component registers a callback for a given signal, it also tells the
+server that it wants to be notified whenever signals of that type are sent by
+some other component.
+"""
+
+
+import logging
+import threading
+
+
+from protobuf.socketrpc import RpcService
+from leap.common.events import (
+ events_pb2 as proto,
+ server,
+ daemon,
+ mac_auth,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+# the `registered_callbacks` dictionary below should have the following
+# format:
+#
+# { event_signal: [ (uid, callback), ... ], ... }
+#
+registered_callbacks = {}
+
+
+class CallbackAlreadyRegistered(Exception):
+ """
+ Raised when trying to register an already registered callback.
+ """
+
+
+def ensure_component_daemon():
+ """
+ Ensure the component daemon is running and listening for incoming
+ messages.
+
+ @return: the daemon instance
+ @rtype: EventsComponentDaemon
+ """
+ daemon = EventsComponentDaemon.ensure(0)
+ import time
+ # Because we use a random port we want to wait until a port is assigned to
+ # local component daemon.
+ while not (EventsComponentDaemon.get_instance() and
+ EventsComponentDaemon.get_instance().get_port()):
+ time.sleep(0.1)
+ return daemon
+
+
+def register(signal, callback, uid=None, replace=False, reqcbk=None,
+ timeout=1000):
+ """
+ Registers a callback to be called when a specific signal event is
+ received.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ @param signal: the signal that causes the callback to be launched
+ @type signal: int (see the `events.proto` file)
+ @param callback: the callback to be called when the signal is received
+ @type callback: function
+ callback(leap.common.events.events_pb2.SignalRequest)
+ @param uid: a unique id for the callback
+ @type uid: int
+ @param replace: should an existent callback with same uid be replaced?
+ @type replace: bool
+ @param reqcbk: a callback to be called when a response from server is
+ received
+ @type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ @param timeout: the timeout for synch calls
+ @type timeout: int
+
+ Might raise a CallbackAlreadyRegistered exception if there's already a
+ callback identified by the given uid and replace is False.
+
+ @return: the response from server for synch calls or nothing for asynch
+ calls
+ @rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ ensure_component_daemon() # so we can receive registered signals
+ # register callback locally
+ if signal not in registered_callbacks:
+ registered_callbacks[signal] = []
+ cbklist = registered_callbacks[signal]
+ if uid and filter(lambda (x, y): x == uid, cbklist):
+ if not replace:
+ raise CallbackAlreadyRegisteredException()
+ else:
+ registered_callbacks[signal] = filter(lambda(x, y): x != uid,
+ cbklist)
+ registered_callbacks[signal].append((uid, callback))
+ # register callback on server
+ request = proto.RegisterRequest()
+ request.event = signal
+ request.port = EventsComponentDaemon.get_instance().get_port()
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(proto.EventsServerService_Stub,
+ server.SERVER_PORT, 'localhost')
+ logger.info("Sending registration request to server: %s", str(request))
+ return service.register(request, callback=reqcbk, timeout=timeout)
+
+
+def signal(signal, content="", mac_method="", mac="", reqcbk=None,
+ timeout=1000):
+ """
+ Send `signal` event to events server.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ @param signal: the signal that causes the callback to be launched
+ @type signal: int (see the `events.proto` file)
+ @param content: the contents of the event signal
+ @type content: str
+ @param mac_method: the method used for auth mac
+ @type mac_method: str
+ @param mac: the content of the auth mac
+ @type mac: str
+ @param reqcbk: a callback to be called when a response from server is
+ received
+ @type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ @param timeout: the timeout for synch calls
+ @type timeout: int
+
+ @return: the response from server for synch calls or nothing for asynch
+ calls
+ @rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ request = proto.SignalRequest()
+ request.event = signal
+ request.content = content
+ request.mac_method = mac_method
+ request.mac = mac
+ service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT,
+ 'localhost')
+ logger.info("Sending signal to server: %s", str(request))
+ return service.signal(request, callback=reqcbk, timeout=timeout)
+
+
+class EventsComponentService(proto.EventsComponentService):
+ """
+ Service for receiving signal events in components.
+ """
+
+ def __init__(self):
+ proto.EventsComponentService.__init__(self)
+
+ def signal(self, controller, request, done):
+ """
+ Receive a signal and run callbacks registered for that signal.
+
+ This method is called whenever a signal request is received from
+ server.
+
+ @param controller: used to mediate a single method call
+ @type controller: protobuf.socketrpc.controller.SocketRpcController
+ @param request: the request received from the component
+ @type request: leap.common.events.events_pb2.SignalRequest
+ @param done: callback to be called when done
+ @type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info('Received signal from server: %s' % str(request))
+
+ # run registered callbacks
+ # TODO: verify authentication using mac in incoming message
+ if request.event in registered_callbacks:
+ for (_, cbk) in registered_callbacks[request.event]:
+ # callbacks should be prepared to receive a
+ # events_pb2.SignalRequest.
+ cbk(request)
+
+ # send response back to server
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+
+class EventsComponentDaemon(daemon.EventsSingletonDaemon):
+ """
+ A daemon that listens for incoming events from server.
+ """
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon is running on the given port.
+
+ @param port: the port in which the daemon should listen
+ @type port: int
+
+ @return: a daemon instance
+ @rtype: EventsComponentDaemon
+ """
+ return cls.ensure_service(port, EventsComponentService())
diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py
new file mode 100644
index 0000000..09f3c2f
--- /dev/null
+++ b/src/leap/common/events/daemon.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# daemon.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+A singleton daemon for running RPC services using protobuf.socketrpc.
+"""
+
+
+import logging
+import threading
+
+
+from protobuf.socketrpc.server import (
+ SocketRpcServer,
+ ThreadedTCPServer,
+ SocketHandler,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+class ServiceAlreadyRunningException(Exception):
+ """
+ Raised whenever a service is already running in this process but someone
+ attemped to start it in a different port.
+ """
+
+
+class EventsRpcServer(SocketRpcServer):
+ """
+ RPC server used in server and component interfaces to receive messages.
+ """
+
+ def __init__(self, port, host='localhost'):
+ """
+ Initialize a RPC server.
+
+ @param port: the port in which to listen for incoming messages
+ @type port: int
+ @param host: the address to bind to
+ @type host: str
+ """
+ SocketRpcServer.__init__(self, port, host)
+ self._server = None
+
+ def run(self):
+ """
+ Run the server.
+ """
+ logger.info('Running server on port %d.' % self.port)
+ # parent implementation does not hold the server instance, so we do it
+ # here.
+ self._server = ThreadedTCPServer((self.host, self.port),
+ SocketHandler, self)
+ # if we chose to use a random port, fetch the port number info.
+ if self.port is 0:
+ self.port = self._server.socket.getsockname()[1]
+ self._server.serve_forever()
+
+ def stop(self):
+ """
+ Stop the server.
+ """
+ self._server.shutdown()
+
+
+class EventsSingletonDaemon(threading.Thread):
+ """
+ Singleton class for for launching and terminating a daemon.
+
+ This class is used so every part of the mechanism that needs to listen for
+ messages can launch its own daemon (thread) to do the job.
+ """
+
+ # Singleton instance
+ __instance = None
+
+ def __new__(cls, *args, **kwargs):
+ """
+ Return a singleton instance if it exists or create and initialize one.
+ """
+ if len(args) is not 2:
+ raise TypeError("__init__() takes exactly 2 arguments (%d given)"
+ % len(args))
+ if cls.__instance is None:
+ cls.__instance = object.__new__(
+ EventsSingletonDaemon, *args, **kwargs)
+ cls.__initialize(cls.__instance, args[0], args[1])
+ return cls.__instance
+
+ @staticmethod
+ def __initialize(self, port, service):
+ """
+ Initialize a singleton daemon.
+
+ This is a static method disguised as instance method that actually
+ does the initialization of the daemon instance.
+
+ @param port: the port in which to listen for incoming messages
+ @type port: int
+ @param service: the service to provide in this daemon
+ @type service: google.protobuf.service.Service
+ """
+ threading.Thread.__init__(self)
+ self._port = port
+ self._service = service
+ self._server = EventsRpcServer(self._port)
+ self._server.registerService(self._service)
+ self.daemon = True
+
+ def __init__(self):
+ """
+ Singleton placeholder initialization method.
+
+ Initialization is made in __new__ so we can always return the same
+ instance upon object creation.
+ """
+ pass
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon instance is running.
+
+ Each implementation of this method should call `self.ensure_service`
+ with the appropriate service from the `events.proto` definitions, and
+ return the daemon instance.
+
+ @param port: the port in which the daemon should be listening
+ @type port: int
+
+ @return: a daemon instance
+ @rtype: EventsSingletonDaemon
+ """
+ raise NotImplementedError(self.ensure)
+
+ @classmethod
+ def ensure_service(cls, port, service):
+ """
+ Start the singleton instance if not already running.
+
+ Might return ServiceAlreadyRunningException
+
+ @param port: the port in which the daemon should be listening
+ @type port: int
+
+ @return: a daemon instance
+ @rtype: EventsSingletonDaemon
+ """
+ daemon = cls(port, service)
+ if not daemon.is_alive():
+ daemon.start()
+ elif port and port != cls.__instance._port:
+ # service is running in this process but someone is trying to
+ # start it in another port
+ raise ServiceAlreadyRunningException(
+ "Service is already running in this process on port %d."
+ % self.__instance._port)
+ return daemon
+
+ @classmethod
+ def get_instance(cls):
+ """
+ Retrieve singleton instance of this daemon.
+
+ @return: a daemon instance
+ @rtype: EventsSingletonDaemon
+ """
+ return cls.__instance
+
+ def run(self):
+ """
+ Run the server.
+ """
+ self._server.run()
+
+ def stop(self):
+ """
+ Stop the daemon.
+ """
+ self._server.stop()
+
+ def get_port(self):
+ """
+ Retrieve the value of the port to which the service running in this
+ daemon is binded to.
+
+ @return: the port to which the daemon is binded to
+ @rtype: int
+ """
+ if self._port is 0:
+ self._port = self._server.port
+ return self._port
diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto
new file mode 100644
index 0000000..29388b8
--- /dev/null
+++ b/src/leap/common/events/events.proto
@@ -0,0 +1,69 @@
+// signal.proto
+// Copyright (C) 2013 LEA
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package leap.common.events;
+
+enum Event {
+ CLIENT_SESSION_ID = 1;
+ CLIENT_UID = 2;
+ SOLEDAD_CREATING_KEYS = 3;
+ SOLEDAD_DONE_CREATING_KEYS = 4;
+ SOLEDAD_UPLOADING_KEYS = 5;
+ SOLEDAD_DONE_UPLOADING_KEYS = 6;
+ SOLEDAD_DOWNLOADING_KEYS = 7;
+ SOLEDAD_DONE_DOWNLOADING_KEYS = 8;
+ SOLEDAD_NEW_DATA_TO_SYNC = 9;
+ SOLEDAD_DONE_DATA_SYNC = 10;
+ UPDATER_NEW_UPDATES = 11;
+ UPDATER_DONE_UPDATING = 12;
+}
+
+message SignalRequest {
+ required Event event = 1;
+ required string content = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+ optional string enc_method = 5;
+ optional bool error_occurred = 6;
+}
+
+message RegisterRequest {
+ required Event event = 1;
+ required int32 port = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+}
+
+message EventResponse {
+
+ enum Status {
+ OK = 1;
+ UNAUTH = 2;
+ ERROR = 3;
+ }
+
+ required Status status = 1;
+ optional string result = 2;
+}
+
+service EventsServerService {
+ rpc register(RegisterRequest) returns (EventResponse);
+ rpc signal(SignalRequest) returns (EventResponse);
+}
+
+service EventsComponentService {
+ rpc signal(SignalRequest) returns (EventResponse);
+}
diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py
new file mode 100644
index 0000000..1d278cc
--- /dev/null
+++ b/src/leap/common/events/events_pb2.py
@@ -0,0 +1,364 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+
+from google.protobuf import descriptor
+from google.protobuf import message
+from google.protobuf import reflection
+from google.protobuf import service
+from google.protobuf import service_reflection
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+
+DESCRIPTOR = descriptor.FileDescriptor(
+ name='events.proto',
+ package='leap.common.events',
+ serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xd5\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x32\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse')
+
+_EVENT = descriptor.EnumDescriptor(
+ name='Event',
+ full_name='leap.common.events.Event',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ descriptor.EnumValueDescriptor(
+ name='CLIENT_SESSION_ID', index=0, number=1,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='CLIENT_UID', index=1, number=2,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_CREATING_KEYS', index=2, number=3,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_UPLOADING_KEYS', index=4, number=5,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='UPDATER_NEW_UPDATES', index=10, number=11,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='UPDATER_DONE_UPDATING', index=11, number=12,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=432,
+ serialized_end=773,
+)
+
+
+CLIENT_SESSION_ID = 1
+CLIENT_UID = 2
+SOLEDAD_CREATING_KEYS = 3
+SOLEDAD_DONE_CREATING_KEYS = 4
+SOLEDAD_UPLOADING_KEYS = 5
+SOLEDAD_DONE_UPLOADING_KEYS = 6
+SOLEDAD_DOWNLOADING_KEYS = 7
+SOLEDAD_DONE_DOWNLOADING_KEYS = 8
+SOLEDAD_NEW_DATA_TO_SYNC = 9
+SOLEDAD_DONE_DATA_SYNC = 10
+UPDATER_NEW_UPDATES = 11
+UPDATER_DONE_UPDATING = 12
+
+
+_EVENTRESPONSE_STATUS = descriptor.EnumDescriptor(
+ name='Status',
+ full_name='leap.common.events.EventResponse.Status',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ descriptor.EnumValueDescriptor(
+ name='OK', index=0, number=1,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='UNAUTH', index=1, number=2,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='ERROR', index=2, number=3,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=390,
+ serialized_end=429,
+)
+
+
+_SIGNALREQUEST = descriptor.Descriptor(
+ name='SignalRequest',
+ full_name='leap.common.events.SignalRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='event', full_name='leap.common.events.SignalRequest.event', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='content', full_name='leap.common.events.SignalRequest.content', index=1,
+ number=2, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2,
+ number=3, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='mac', full_name='leap.common.events.SignalRequest.mac', index=3,
+ number=4, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value="",
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5,
+ number=6, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=37,
+ serialized_end=188,
+)
+
+
+_REGISTERREQUEST = descriptor.Descriptor(
+ name='RegisterRequest',
+ full_name='leap.common.events.RegisterRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='event', full_name='leap.common.events.RegisterRequest.event', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='port', full_name='leap.common.events.RegisterRequest.port', index=1,
+ number=2, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2,
+ number=3, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3,
+ number=4, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value="",
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=190,
+ serialized_end=296,
+)
+
+
+_EVENTRESPONSE = descriptor.Descriptor(
+ name='EventResponse',
+ full_name='leap.common.events.EventResponse',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='status', full_name='leap.common.events.EventResponse.status', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='result', full_name='leap.common.events.EventResponse.result', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ _EVENTRESPONSE_STATUS,
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=299,
+ serialized_end=429,
+)
+
+
+_SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT
+_REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT
+_EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS
+_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE
+
+
+class SignalRequest(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _SIGNALREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest)
+
+
+class RegisterRequest(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _REGISTERREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest)
+
+
+class EventResponse(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _EVENTRESPONSE
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse)
+
+
+_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor(
+ name='EventsServerService',
+ full_name='leap.common.events.EventsServerService',
+ file=DESCRIPTOR,
+ index=0,
+ options=None,
+ serialized_start=776,
+ serialized_end=961,
+ methods=[
+ descriptor.MethodDescriptor(
+ name='register',
+ full_name='leap.common.events.EventsServerService.register',
+ index=0,
+ containing_service=None,
+ input_type=_REGISTERREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsServerService.signal',
+ index=1,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
+
+
+class EventsServerService(service.Service):
+ __metaclass__ = service_reflection.GeneratedServiceType
+ DESCRIPTOR = _EVENTSSERVERSERVICE
+
+
+class EventsServerService_Stub(EventsServerService):
+ __metaclass__ = service_reflection.GeneratedServiceStubType
+ DESCRIPTOR = _EVENTSSERVERSERVICE
+
+
+_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor(
+ name='EventsComponentService',
+ full_name='leap.common.events.EventsComponentService',
+ file=DESCRIPTOR,
+ index=1,
+ options=None,
+ serialized_start=963,
+ serialized_end=1067,
+ methods=[
+ descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsComponentService.signal',
+ index=0,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
+
+
+class EventsComponentService(service.Service):
+ __metaclass__ = service_reflection.GeneratedServiceType
+ DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+
+
+class EventsComponentService_Stub(EventsComponentService):
+ __metaclass__ = service_reflection.GeneratedServiceStubType
+ DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/leap/common/events/mac_auth.py b/src/leap/common/events/mac_auth.py
new file mode 100644
index 0000000..49d48f7
--- /dev/null
+++ b/src/leap/common/events/mac_auth.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# mac_auth.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Authentication system for events.
+
+This is not implemented yet.
+"""
+
+
+class MacMethod(object):
+ """
+ Representation of possible MAC authentication methods.
+ """
+
+ MAC_NONE = 'none'
+ MAC_HMAC = 'hmac'
diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py
new file mode 100644
index 0000000..5cc1add
--- /dev/null
+++ b/src/leap/common/events/server.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+# server.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+A server for the events mechanism.
+
+A server can receive different kinds of requests from components:
+
+ 1. Registration request: store component port number to be notified when
+ a specific signal arrives.
+
+ 2. Signal request: redistribute the signal to registered components.
+"""
+
+
+import logging
+import sets
+
+
+from protobuf.socketrpc import RpcService
+from leap.common.events import (
+ events_pb2 as proto,
+ daemon,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+SERVER_PORT = 8090
+
+# the `registered_components` dictionary below should have the following
+# format:
+#
+# { event_signal: [ port, ... ], ... }
+#
+registered_components = {}
+
+
+def ensure_server(port=SERVER_PORT):
+ """
+ Make sure the server is running on the given port.
+
+ Attempt to connect to given local port. Upon success, assume that the
+ events server has already been started. Upon failure, start events server.
+
+ @param port: the port in which server should be listening
+ @type port: int
+
+ @return: the daemon instance or nothing
+ @rtype: EventsServerDaemon or None
+ """
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', port))
+ s.close()
+ logger.info('Server is already running on port %d.', port)
+ return None
+ except socket.error:
+ logger.info('Launching server on port %d.', port)
+ return EventsServerDaemon.ensure(port)
+
+
+class EventsServerService(proto.EventsServerService):
+ """
+ Service for receiving events in components.
+ """
+
+ def register(self, controller, request, done):
+ """
+ Register a component port to be signaled when specific events come in.
+
+ @param controller: used to mediate a single method call
+ @type controller: protobuf.socketrpc.controller.SocketRpcController
+ @param request: the request received from the component
+ @type request: leap.common.events.events_pb2.RegisterRequest
+ @param done: callback to be called when done
+ @type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info("Received registration request: %s" % str(request))
+ # add component port to signal list
+ if request.event not in registered_components:
+ registered_components[request.event] = sets.Set()
+ registered_components[request.event].add(request.port)
+ # send response back to component
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+ def signal(self, controller, request, done):
+ """
+ Perform an RPC call to signal all components registered to receive a
+ specific signal.
+
+ @param controller: used to mediate a single method call
+ @type controller: protobuf.socketrpc.controller.SocketRpcController
+ @param request: the request received from the component
+ @type request: leap.common.events.events_pb2.SignalRequest
+ @param done: callback to be called when done
+ @type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info('Received signal from component: %s', str(request))
+ # send signal to all registered components
+ # TODO: verify signal auth
+ if request.event in registered_components:
+ for port in registered_components[request.event]:
+
+ def callback(req, resp):
+ logger.info("Signal received by " + str(port))
+
+ service = RpcService(proto.EventsComponentService_Stub,
+ port, 'localhost')
+ service.signal(request, callback=callback)
+ # send response back to component
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+
+class EventsServerDaemon(daemon.EventsSingletonDaemon):
+ """
+ Singleton class for starting an events server daemon.
+ """
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon is running on the given port.
+
+ @param port: the port in which the daemon should listen
+ @type port: int
+
+ @return: a daemon instance
+ @rtype: EventsServerDaemon
+ """
+ return cls.ensure_service(port, EventsServerService())