summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/common/__init__.py2
-rw-r--r--src/leap/common/check.py26
-rw-r--r--src/leap/common/config/baseconfig.py65
-rw-r--r--src/leap/common/config/prefixers.py2
-rw-r--r--src/leap/common/config/tests/test_baseconfig.py268
-rw-r--r--src/leap/common/crypto.py114
-rw-r--r--src/leap/common/events/README.rst12
-rw-r--r--src/leap/common/events/__init__.py128
-rw-r--r--src/leap/common/events/client.py (renamed from src/leap/common/events/component.py)155
-rw-r--r--src/leap/common/events/daemon.py2
-rw-r--r--src/leap/common/events/events.proto44
-rw-r--r--src/leap/common/events/events_pb2.py550
-rw-r--r--src/leap/common/events/server.py132
-rw-r--r--src/leap/common/testing/basetest.py3
-rw-r--r--src/leap/common/tests/test_check.py52
-rw-r--r--src/leap/common/tests/test_crypto.py88
-rw-r--r--src/leap/common/tests/test_events.py316
17 files changed, 1406 insertions, 553 deletions
diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py
index 2b30715..3946fe8 100644
--- a/src/leap/common/__init__.py
+++ b/src/leap/common/__init__.py
@@ -16,4 +16,4 @@ except ImportError:
__all__ = ["certs", "check", "files", "events"]
-__version__ = "0.2.5"
+__version__ = "0.3.0"
diff --git a/src/leap/common/check.py b/src/leap/common/check.py
index a2d39a6..82da5ff 100644
--- a/src/leap/common/check.py
+++ b/src/leap/common/check.py
@@ -18,7 +18,6 @@
Set of functions to help checking situations
"""
-import inspect
import logging
import traceback
@@ -39,9 +38,11 @@ def leap_assert(condition, message=""):
if not condition:
logger.error("Bug: %s" % (message,))
try:
- frame = inspect.currentframe()
- stack_trace = traceback.format_stack(frame)
- logger.error(''.join(stack_trace))
+ for formatted_line in traceback.format_list(
+ traceback.extract_stack()[:-1]):
+ for line in formatted_line.split("\n"):
+ if len(line.strip()) > 0:
+ logger.error(line)
except Exception as e:
logger.error("Bug in leap_assert: %r" % (e,))
assert condition, message
@@ -59,3 +60,20 @@ def leap_assert_type(var, expectedType):
leap_assert(isinstance(var, expectedType),
"Expected type %r instead of %r" %
(expectedType, type(var)))
+
+
+def leap_check(condition, message="", exception=Exception):
+ """
+ Asserts the condition and displays the message if that's not
+ met. It also logs the error and its backtrace.
+
+ :param condition: condition to check
+ :type condition: bool
+ :param message: message to display if the condition isn't met
+ :type message: str
+ :param exception: the exception to raise in case of condition being false.
+ :type exception: Exception
+ """
+ if not condition:
+ logger.error(message)
+ raise exception(message)
diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py
index e6bd9c4..e310bc0 100644
--- a/src/leap/common/config/baseconfig.py
+++ b/src/leap/common/config/baseconfig.py
@@ -26,7 +26,7 @@ import os
from abc import ABCMeta, abstractmethod
-from leap.common.check import leap_assert
+from leap.common.check import leap_assert, leap_check
from leap.common.files import mkdir_p
from leap.common.config.pluggableconfig import PluggableConfig
from leap.common.config.prefixers import get_platform_prefixer
@@ -34,6 +34,12 @@ from leap.common.config.prefixers import get_platform_prefixer
logger = logging.getLogger(__name__)
+class NonExistingSchema(Exception):
+ """
+ Raised if the schema needed to verify the config is None.
+ """
+
+
class BaseConfig:
"""
Abstract base class for any JSON based configuration.
@@ -55,13 +61,27 @@ class BaseConfig:
def __init__(self):
self._data = {}
self._config_checker = None
+ self._api_version = None
@abstractmethod
+ def _get_schema(self):
+ """
+ Returns the schema corresponding to the version given.
+
+ :rtype: dict or None if the version is not supported.
+ """
+ pass
+
def _get_spec(self):
"""
Returns the spec object for the specific configuration.
+
+ :rtype: dict or None if the version is not supported.
"""
- return None
+ leap_assert(self._api_version is not None,
+ "You should set the API version.")
+
+ return self._get_schema()
def _safe_get_value(self, key):
"""
@@ -73,6 +93,17 @@ class BaseConfig:
leap_assert(self._config_checker, "Load the config first")
return self._config_checker.config.get(key, None)
+ def set_api_version(self, version):
+ """
+ Sets the supported api version.
+
+ :param api_version: the version of the api supported by the provider.
+ :type api_version: str
+ """
+ self._api_version = version
+ leap_assert(self._get_schema() is not None,
+ "Version %s is not supported." % (version, ))
+
def get_path_prefix(self):
"""
Returns the platform dependant path prefixer
@@ -112,6 +143,7 @@ class BaseConfig:
def load(self, path="", data=None, mtime=None, relative=True):
"""
Loads the configuration from disk.
+ It may raise NonExistingSchema exception.
:param path: if relative=True, this is a relative path
to configuration. The absolute path
@@ -131,8 +163,12 @@ class BaseConfig:
else:
config_path = path
+ schema = self._get_spec()
+ leap_check(schema is not None,
+ "There is no schema to use.", NonExistingSchema)
+
self._config_checker = PluggableConfig(format="json")
- self._config_checker.options = copy.deepcopy(self._get_spec())
+ self._config_checker.options = copy.deepcopy(schema)
try:
if data is None:
@@ -155,26 +191,39 @@ class LocalizedKey(object):
def __init__(self, func, **kwargs):
self._func = func
- def __call__(self, instance, lang="en"):
+ def __call__(self, instance, lang=None):
"""
Tries to return the string for the specified language, otherwise
- informs the problem and returns an empty string.
+ returns the default language string.
:param lang: language code
:type lang: str
:return: localized value from the possible values returned by
self._func
+ It returns None in case that the provider does not provides
+ a matching pair of default_language and string for
+ that language.
+ e.g.:
+ 'default_language': 'es',
+ 'description': {'en': 'test description'}
+ Note that the json schema can't check that.
"""
descriptions = self._func(instance)
- description_lang = ""
- config_lang = "en"
+ config_lang = instance.get_default_language()
+ if lang is None:
+ lang = config_lang
+
for key in descriptions.keys():
if lang.startswith(key):
config_lang = key
break
- description_lang = descriptions[config_lang]
+ description_lang = descriptions.get(config_lang)
+ if description_lang is None:
+ logger.error("There is a misconfiguration in the "
+ "provider's language strings.")
+
return description_lang
def __get__(self, instance, instancetype):
diff --git a/src/leap/common/config/prefixers.py b/src/leap/common/config/prefixers.py
index 050d4cd..9a5b043 100644
--- a/src/leap/common/config/prefixers.py
+++ b/src/leap/common/config/prefixers.py
@@ -95,7 +95,7 @@ class DarwinPrefixer(Prefixer):
config_dir = BaseDirectory.xdg_config_home
if not standalone:
return config_dir
- return os.getenv(os.getcwd(), "config")
+ return os.path.join(os.getcwd(), "config")
class WindowsPrefixer(Prefixer):
diff --git a/src/leap/common/config/tests/test_baseconfig.py b/src/leap/common/config/tests/test_baseconfig.py
new file mode 100644
index 0000000..8a2915e
--- /dev/null
+++ b/src/leap/common/config/tests/test_baseconfig.py
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+# test_baseconfig.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/>.
+"""
+Tests for baseconfig
+"""
+import json
+import unittest
+import copy
+
+from leap.common.config.baseconfig import BaseConfig, LocalizedKey
+from leap.common.testing.basetest import BaseLeapTest
+
+from mock import Mock
+
+# reduced eipconfig sample config
+sample_config = {
+ "gateways": [
+ {
+ "capabilities": {
+ "adblock": False,
+ "transport": ["openvpn"],
+ "user_ips": False
+ },
+ "host": "host.dev.example.org",
+ }, {
+ "capabilities": {
+ "adblock": False,
+ "transport": ["openvpn"],
+ "user_ips": False
+ },
+ "host": "host2.dev.example.org",
+ }
+ ],
+ "default_language": "en",
+ "languages": [
+ "en",
+ "es"
+ ],
+ "name": {
+ "en": "Baseconfig testing environment",
+ "es": "Entorno de pruebas de Baseconfig"
+ },
+ "serial": 1,
+ "version": 1
+}
+
+# reduced eipconfig.spec version
+sample_spec = {
+ 'description': 'sample eip service config',
+ 'type': 'object',
+ 'properties': {
+ 'serial': {
+ 'type': int,
+ 'default': 1,
+ 'required': ["True"]
+ },
+ 'version': {
+ 'type': int,
+ 'default': 1,
+ 'required': ["True"]
+ },
+ "default_language": {
+ 'type': unicode,
+ 'default': 'en'
+ },
+ 'languages': {
+ 'type': list,
+ 'default': ['en']
+ },
+ 'name': {
+ 'type': dict,
+ 'format': 'translatable',
+ 'default': {u'en': u'Test Provider'}
+ },
+ 'gateways': {
+ 'type': list,
+ 'default': [
+ {"capabilities": {
+ "adblock": True,
+ "transport": ["openvpn"],
+ "user_ips": False},
+ "host": "location.example.org",
+ }]
+ },
+ }
+}
+
+
+class TestConfig(BaseConfig):
+ """
+ BaseConfig implementation for testing purposes only.
+ """
+ def get_gateways(self):
+ return self._safe_get_value("gateways")
+
+ def get_serial(self):
+ return self._safe_get_value("serial")
+
+ def get_version(self):
+ return self._safe_get_value("version")
+
+ def _get_spec(self):
+ return sample_spec
+
+ def get_default_language(self):
+ return self._safe_get_value("default_language")
+
+ @LocalizedKey
+ def get_name(self):
+ return self._safe_get_value("name")
+
+
+class BaseConfigTest(BaseLeapTest):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def _write_config(self, data):
+ """
+ Helper to write some data to a temp config file.
+
+ :param data: data to be used to save in the config file.
+ :data type: dict (valid json)
+ """
+ self.config_file = self.get_tempfile("config.json")
+ conf = open(self.config_file, "w")
+ conf.write(json.dumps(data))
+ conf.close()
+
+ def _get_config(self, fromfile=False, data=sample_config):
+ """
+ Helper that returns a TestConfig object using the data parameter
+ or a sample data.
+
+ :param fromfile: sets if we should use a file or a string
+ :fromfile type: bool
+ :param data: sets the data to be used to load in the TestConfig object
+ :data type: dict (valid json)
+ :rtype: TestConfig
+ """
+ config = TestConfig()
+
+ loaded = False
+ if fromfile:
+ self._write_config(data)
+ loaded = config.load(self.config_file, relative=False)
+ else:
+ json_string = json.dumps(data)
+ loaded = config.load(data=json_string)
+
+ if not loaded:
+ return None
+
+ return config
+
+ def test_loads_from_file(self):
+ config = self._get_config(fromfile=True)
+ self.assertIsNotNone(config)
+
+ def test_loads_from_data(self):
+ config = self._get_config()
+ self.assertIsNotNone(config)
+
+ def test_load_valid_config_from_file(self):
+ config = self._get_config(fromfile=True)
+ self.assertIsNotNone(config)
+
+ self.assertEqual(config.get_version(), sample_config["version"])
+ self.assertEqual(config.get_serial(), sample_config["serial"])
+ self.assertEqual(config.get_gateways(), sample_config["gateways"])
+
+ def test_load_valid_config_from_data(self):
+ config = self._get_config()
+ self.assertIsNotNone(config)
+
+ self.assertEqual(config.get_version(), sample_config["version"])
+ self.assertEqual(config.get_serial(), sample_config["serial"])
+ self.assertEqual(config.get_gateways(), sample_config["gateways"])
+
+ def test_safe_get_value_no_config(self):
+ config = TestConfig()
+
+ with self.assertRaises(AssertionError):
+ config.get_version()
+
+ def test_safe_get_value_non_existent_value(self):
+ config = self._get_config()
+
+ self.assertIsNone(config._safe_get_value('non-existent-value'))
+
+ def test_loaded(self):
+ config = self._get_config()
+ self.assertTrue(config.loaded())
+
+ def test_not_loaded(self):
+ config = TestConfig()
+ self.assertFalse(config.loaded())
+
+ def test_save_and_load(self):
+ config = self._get_config()
+ config.get_path_prefix = Mock(return_value=self.tempdir)
+ config_file = 'test_config.json'
+ self.assertTrue(config.save([config_file]))
+
+ config_saved = TestConfig()
+ config_file_path = self.get_tempfile(config_file)
+ self.assertTrue(config_saved.load(config_file_path, relative=False))
+
+ self.assertEqual(config.get_version(), config_saved.get_version())
+ self.assertEqual(config.get_serial(), config_saved.get_serial())
+ self.assertEqual(config.get_gateways(), config_saved.get_gateways())
+
+ def test_localizations(self):
+ conf = self._get_config()
+
+ self.assertEqual(conf.get_name(lang='en'), sample_config['name']['en'])
+ self.assertEqual(conf.get_name(lang='es'), sample_config['name']['es'])
+
+ def _localized_config(self, lang):
+ """
+ Helper to change default language of the provider config.
+ """
+ conf = copy.deepcopy(sample_config)
+ conf['default_language'] = lang
+ json_string = json.dumps(conf)
+ config = TestConfig()
+ config.load(data=json_string)
+
+ return config
+
+ def test_default_localization1(self):
+ default_language = sample_config['languages'][0]
+ config = self._localized_config(default_language)
+
+ default_name = sample_config['name'][default_language]
+
+ self.assertEqual(config.get_name(lang='xx'), default_name)
+ self.assertEqual(config.get_name(), default_name)
+
+ def test_default_localization2(self):
+ default_language = sample_config['languages'][1]
+ config = self._localized_config(default_language)
+
+ default_name = sample_config['name'][default_language]
+
+ self.assertEqual(config.get_name(lang='xx'), default_name)
+ self.assertEqual(config.get_name(), default_name)
+
+
+if __name__ == "__main__":
+ unittest.main(verbosity=2)
diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py
deleted file mode 100644
index 7f80a8a..0000000
--- a/src/leap/common/crypto.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- coding: utf-8 -*-
-# crypto.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/>.
-
-import os
-import binascii
-
-from Crypto.Cipher import AES
-from Crypto.Util import Counter
-from leap.common.check import leap_assert, leap_assert_type
-
-#
-# encryption methods
-#
-
-class EncryptionMethods(object):
- """
- Representation of encryption methods that can be used.
- """
-
- AES_256_CTR = 'aes-256-ctr'
-
-
-class UnknownEncryptionMethod(Exception):
- """
- Raised when trying to encrypt/decrypt with unknown method.
- """
- pass
-
-
-#
-# encrypt/decrypt functions
-#
-
-# In the future, we might want to implement other encryption schemes and
-# possibly factor out the actual encryption/decryption routines of the
-# following functions to specific classes, while not changing the API.
-
-def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR):
- """
- Encrypt C{data} with C{key}, using C{method} encryption method.
-
- :param data: The data to be encrypted.
- :type data: str
- :param key: The key used to encrypt C{data} (must be 256 bits long).
- :type key: str
- :param method: The encryption method to use.
- :type method: str
-
- :return: A tuple with the initial value and the encrypted data.
- :rtype: (long, str)
- """
- leap_assert_type(key, str)
-
- # AES-256 in CTR mode
- if method == EncryptionMethods.AES_256_CTR:
- leap_assert(
- len(key) == 32, # 32 x 8 = 256 bits.
- 'Wrong key size: %s bits (must be 256 bits long).' % (len(key)*8))
- iv = os.urandom(8)
- ctr = Counter.new(64, prefix=iv)
- cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
- return binascii.b2a_base64(iv), cipher.encrypt(data)
-
- # raise if method is unknown
- raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
-
-
-def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs):
- """
- Decrypt C{data} with C{key} using C{method} encryption method.
-
- :param data: The data to be decrypted.
- :type data: str
- :param key: The key used to decrypt C{data} (must be 256 bits long).
- :type key: str
- :param method: The encryption method to use.
- :type method: str
- :param kwargs: Other parameters specific to each encryption method.
- :type kwargs: dict
-
- :return: The decrypted data.
- :rtype: str
- """
- leap_assert_type(key, str)
-
- # AES-256 in CTR mode
- if method == EncryptionMethods.AES_256_CTR:
- # assert params
- leap_assert(
- len(key) == 32, # 32 x 8 = 256 bits.
- 'Wrong key size: %s (must be 256 bits long).' % len(key))
- leap_assert(
- 'iv' in kwargs,
- 'AES-256-CTR needs an initial value given as.')
- ctr = Counter.new(64, prefix=binascii.a2b_base64(kwargs['iv']))
- cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
- return cipher.decrypt(data)
-
- # raise if method is unknown
- raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
diff --git a/src/leap/common/events/README.rst b/src/leap/common/events/README.rst
index 813be8b..2e7f254 100644
--- a/src/leap/common/events/README.rst
+++ b/src/leap/common/events/README.rst
@@ -1,19 +1,19 @@
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
+The events mechanism allows for clients to send signal events to each
+other by means of a centralized server. Clients 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.
+the server that will then redistribute these signals to registered clients.
Listening daemons
-----------------
-Both components and the server listen for incoming messages by using a
+Both clients 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.
+started explicitly, while clients daemon will be started whenever a
+client registers with the server to receive messages.
How to use it
diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py
index 9fc93ee..6eaf3d4 100644
--- a/src/leap/common/events/__init__.py
+++ b/src/leap/common/events/__init__.py
@@ -16,7 +16,58 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-An events mechanism that allows for signaling of events between components.
+This is an events mechanism that uses a server to allow for signaling of
+events between clients.
+
+Application components should use the interface available in this file to
+register callbacks to be executed upon receiving specific signals, and to send
+signals to other components.
+
+To register a callback to be executed when a specific event is signaled, use
+leap.common.events.register():
+
+>>> from leap.common.events import register
+>>> from leap.common.events.proto import CLIENT_UID
+>>> register(CLIENT_UID, lambda req: do_something(req))
+
+To signal an event, use leap.common.events.signal():
+
+>>> from leap.common.events import signal
+>>> from leap.common.events.proto import CLIENT_UID
+>>> signal(CLIENT_UID)
+
+
+NOTE ABOUT SYNC/ASYNC REQUESTS:
+
+Clients always communicate with the server, and never between themselves.
+When a client registers a callback for an event, the callback is saved locally
+in the client and the server stores the client socket port in a list
+associated with that event. When a client signals an event, the server
+forwards the signal to all registered client ports, and then each client
+executes its callbacks associated with that event locally.
+
+Each RPC call from a client to the server is followed by a response from the
+server to the client. Calls to register() and signal() (and all other RPC
+calls) can be synchronous or asynchronous meaning if they will or not wait for
+the server's response before returning.
+
+This mechanism was built on top of protobuf.socketrpc, and because of this RPC
+calls are made synchronous or asynchronous in the following way:
+
+ * If RPC calls receive a parameter called `reqcbk`, then the call is made
+ asynchronous. That means that:
+
+ - an eventual `timeout` parameter is not used,
+ - the call returns immediatelly with value None, and
+ - the `reqcbk` callback is executed asynchronously upon the arrival of
+ a response from the server.
+
+ * Otherwise, if the `reqcbk` parameter is None, then the call is made in a
+ synchronous manner:
+
+ - if a response from server arrives within `timeout` milliseconds, the
+ RPC call returns it;
+ - otherwise, the call returns None.
"""
import logging
@@ -24,9 +75,9 @@ import socket
from leap.common.events import (
- events_pb2,
+ events_pb2 as proto,
server,
- component,
+ client,
daemon,
)
@@ -54,17 +105,40 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None,
: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)
+ received
+ :type reqcbk: function(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 client.register(signal, callback, uid, replace, reqcbk, timeout)
+
+
+def unregister(signal, uid=None, reqcbk=None, timeout=1000):
+ """
+ Unregister a callback.
+
+ If C{uid} is specified, unregisters only the callback identified by that
+ unique id. Otherwise, unregisters all callbacks registered for C{signal}.
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param uid: a unique id for the callback
+ :type uid: int
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function(proto.UnregisterRequest, proto.EventResponse)
:param timeout: the timeout for synch calls
:type timeout: int
:return: the response from server for synch calls or nothing for asynch
- calls
+ calls.
:rtype: leap.common.events.events_pb2.EventsResponse or None
"""
- return component.register(signal, callback, uid, replace, reqcbk, timeout)
+ return client.unregister(signal, uid, reqcbk, timeout)
def signal(signal, content="", mac_method="", mac="", reqcbk=None,
@@ -87,14 +161,42 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None,
: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)
+ received
+ :type reqcbk: function(proto.SignalRequest, proto.EventResponse)
:param timeout: the timeout for synch calls
:type timeout: int
:return: the response from server for synch calls or nothing for asynch
- calls
+ calls.
:rtype: leap.common.events.events_pb2.EventsResponse or None
"""
- return component.signal(signal, content, mac_method, mac, reqcbk, timeout)
+ return client.signal(signal, content, mac_method, mac, reqcbk, timeout)
+
+def ping_client(port, reqcbk=None, timeout=1000):
+ """
+ Ping a client running in C{port}.
+
+ :param port: the port in which the client should be listening
+ :type port: int
+ :param reqcbk: a callback to be called when a response from client is
+ received
+ :type reqcbk: function(proto.PingRequest, proto.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+ """
+ return client.ping(port, reqcbk=reqcbk, timeout=timeout)
+
+
+def ping_server(port=server.SERVER_PORT, reqcbk=None, timeout=1000):
+ """
+ Ping the server.
+
+ :param port: the port in which server should be listening
+ :type port: int
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function(proto.PingRequest, proto.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+ """
+ return server.ping(port, reqcbk=reqcbk, timeout=timeout)
diff --git a/src/leap/common/events/component.py b/src/leap/common/events/client.py
index 9932190..4ae9bff 100644
--- a/src/leap/common/events/component.py
+++ b/src/leap/common/events/client.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# component.py
+# client.py
# Copyright (C) 2013 LEAP
#
# This program is free software: you can redistribute it and/or modify
@@ -16,15 +16,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-The component end point of the events mechanism.
+The client end point of the events mechanism.
-Components are the communicating parties of the events mechanism. They
+Clients are the communicating parties of the events mechanism. They
communicate by sending messages to a server, which in turn redistributes
-messages to other components.
+messages to other clients.
-When a component registers a callback for a given signal, it also tells the
+When a client 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.
+some other client.
"""
@@ -56,25 +56,26 @@ class CallbackAlreadyRegistered(Exception):
"""
Raised when trying to register an already registered callback.
"""
+ pass
-def ensure_component_daemon():
+def ensure_client_daemon():
"""
- Ensure the component daemon is running and listening for incoming
+ Ensure the client daemon is running and listening for incoming
messages.
:return: the daemon instance
- :rtype: EventsComponentDaemon
+ :rtype: EventsClientDaemon
"""
import time
- daemon = EventsComponentDaemon.ensure(0)
- logger.debug('ensure component daemon')
+ daemon = EventsClientDaemon.ensure(0)
+ logger.debug('ensure client daemon')
# Because we use a random port we want to wait until a port is assigned to
- # local component daemon.
+ # local client daemon.
- while not (EventsComponentDaemon.get_instance() and
- EventsComponentDaemon.get_instance().get_port()):
+ while not (EventsClientDaemon.get_instance() and
+ EventsClientDaemon.get_instance().get_port()):
time.sleep(0.1)
return daemon
@@ -94,16 +95,14 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None,
: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)
+ :type callback: function(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)
+ received
+ :type reqcbk: function(proto.RegisterRequest, proto.EventResponse)
:param timeout: the timeout for synch calls
:type timeout: int
@@ -111,10 +110,10 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None,
callback identified by the given uid and replace is False.
:return: the response from server for synch calls or nothing for asynch
- calls
+ calls.
:rtype: leap.common.events.events_pb2.EventsResponse or None
"""
- ensure_component_daemon() # so we can receive registered signals
+ ensure_client_daemon() # so we can receive registered signals
# register callback locally
if signal not in registered_callbacks:
registered_callbacks[signal] = []
@@ -129,7 +128,7 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None,
# register callback on server
request = proto.RegisterRequest()
request.event = signal
- request.port = EventsComponentDaemon.get_instance().get_port()
+ request.port = EventsClientDaemon.get_instance().get_port()
request.mac_method = mac_auth.MacMethod.MAC_NONE
request.mac = ""
service = RpcService(proto.EventsServerService_Stub,
@@ -140,6 +139,56 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None,
str(request)[:40])
return service.register(request, callback=reqcbk, timeout=timeout)
+def unregister(signal, uid=None, reqcbk=None, timeout=1000):
+ """
+ Unregister a callback.
+
+ If C{uid} is specified, unregisters only the callback identified by that
+ unique id. Otherwise, unregisters all callbacks
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param uid: a unique id for the callback
+ :type uid: int
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function(proto.UnregisterRequest, proto.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls or None if no callback is registered for that signal or
+ uid.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ if signal not in registered_callbacks or not registered_callbacks[signal]:
+ logger.warning("No callback registered for signal %d." % signal)
+ return None
+ # unregister callback locally
+ cbklist = registered_callbacks[signal]
+ if uid is not None:
+ if filter(lambda (cbkuid, _): cbkuid == uid, cbklist) == []:
+ logger.warning("No callback registered for uid %d." % st)
+ return None
+ registered_callbacks[signal] = filter(lambda(x, y): x != uid, cbklist)
+ else:
+ # exclude all callbacks for given signal
+ registered_callbacks[signal] = []
+ # unregister port in server if there are no more callbacks for this signal
+ if not registered_callbacks[signal]:
+ request = proto.UnregisterRequest()
+ request.event = signal
+ request.port = EventsClientDaemon.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 unregistration request to server on port %s: %s",
+ server.SERVER_PORT,
+ str(request)[:40])
+ return service.unregister(request, callback=reqcbk, timeout=timeout)
+
def signal(signal, content="", mac_method="", mac="", reqcbk=None,
timeout=1000):
@@ -161,14 +210,13 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None,
: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)
+ received
+ :type reqcbk: function(proto.SignalRequest, proto.EventResponse)
:param timeout: the timeout for synch calls
:type timeout: int
:return: the response from server for synch calls or nothing for asynch
- calls
+ calls.
:rtype: leap.common.events.events_pb2.EventsResponse or None
"""
request = proto.SignalRequest()
@@ -182,13 +230,38 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None,
return service.signal(request, callback=reqcbk, timeout=timeout)
-class EventsComponentService(proto.EventsComponentService):
+def ping(port, reqcbk=None, timeout=1000):
"""
- Service for receiving signal events in components.
+ Ping a client running in C{port}.
+
+ :param port: the port in which the client should be listening
+ :type port: int
+ :param reqcbk: a callback to be called when a response from client is
+ received
+ :type reqcbk: function(proto.PingRequest, proto.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from client for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ request = proto.PingRequest()
+ service = RpcService(
+ proto.EventsClientService_Stub,
+ port,
+ 'localhost')
+ logger.info("Pinging a client in port %d..." % port)
+ return service.ping(request, callback=reqcbk, timeout=timeout)
+
+
+class EventsClientService(proto.EventsClientService):
+ """
+ Service for receiving signal events in clients.
"""
def __init__(self):
- proto.EventsComponentService.__init__(self)
+ proto.EventsClientService.__init__(self)
def signal(self, controller, request, done):
"""
@@ -199,7 +272,7 @@ class EventsComponentService(proto.EventsComponentService):
:param controller: used to mediate a single method call
:type controller: protobuf.socketrpc.controller.SocketRpcController
- :param request: the request received from the component
+ :param request: the request received from the client
:type request: leap.common.events.events_pb2.SignalRequest
:param done: callback to be called when done
:type done: protobuf.socketrpc.server.Callback
@@ -219,8 +292,24 @@ class EventsComponentService(proto.EventsComponentService):
response.status = proto.EventResponse.OK
done.run(response)
+ def ping(self, controller, request, done):
+ """
+ Reply to a ping request.
+
+ :param controller: used to mediate a single method call
+ :type controller: protobuf.socketrpc.controller.SocketRpcController
+ :param request: the request received from the client
+ :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 ping request, sending response.")
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
-class EventsComponentDaemon(daemon.EventsSingletonDaemon):
+class EventsClientDaemon(daemon.EventsSingletonDaemon):
"""
A daemon that listens for incoming events from server.
"""
@@ -234,6 +323,6 @@ class EventsComponentDaemon(daemon.EventsSingletonDaemon):
:type port: int
:return: a daemon instance
- :rtype: EventsComponentDaemon
+ :rtype: EventsClientDaemon
"""
- return cls.ensure_service(port, EventsComponentService())
+ return cls.ensure_service(port, EventsClientService())
diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py
index c253948..c4a4189 100644
--- a/src/leap/common/events/daemon.py
+++ b/src/leap/common/events/daemon.py
@@ -43,7 +43,7 @@ class ServiceAlreadyRunningException(Exception):
class EventsRpcServer(SocketRpcServer):
"""
- RPC server used in server and component interfaces to receive messages.
+ RPC server used in server and client interfaces to receive messages.
"""
def __init__(self, port, host='localhost'):
diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto
index 447b038..b844f42 100644
--- a/src/leap/common/events/events.proto
+++ b/src/leap/common/events/events.proto
@@ -17,6 +17,9 @@
package leap.common.events;
option py_generic_services = true;
+
+// These are the events that can be signaled using the events mechanism.
+
enum Event {
CLIENT_SESSION_ID = 1;
CLIENT_UID = 2;
@@ -33,6 +36,10 @@ enum Event {
RAISE_WINDOW = 13;
}
+
+// A SignalRequest is the type of the message sent from one component to request
+// that a signal be sent to every registered component.
+
message SignalRequest {
required Event event = 1;
required string content = 2;
@@ -42,6 +49,10 @@ message SignalRequest {
optional bool error_occurred = 6;
}
+
+// A RegisterRequest message tells the server that a component wants to
+// be signaled whenever a specific event occurs.
+
message RegisterRequest {
required Event event = 1;
required int32 port = 2;
@@ -49,6 +60,28 @@ message RegisterRequest {
required bytes mac = 4;
}
+
+// An UnregisterRequest message tells the server that a component does not
+// want to be signaled when a specific event occurs.
+
+message UnregisterRequest {
+ required Event event = 1;
+ required int32 port = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+}
+
+
+// A PingRequest message is used to find out if a server or component is
+// alive.
+
+message PingRequest {
+}
+
+
+// The EventResponse is the message sent back by server and components after
+// they receive other kinds of requests.
+
message EventResponse {
enum Status {
@@ -61,11 +94,20 @@ message EventResponse {
optional string result = 2;
}
+
+// The EventsServerService is the service provided by the server.
+
service EventsServerService {
+ rpc ping(PingRequest) returns (EventResponse);
rpc register(RegisterRequest) returns (EventResponse);
+ rpc unregister(UnregisterRequest) returns (EventResponse);
rpc signal(SignalRequest) returns (EventResponse);
}
-service EventsComponentService {
+
+// EventsComponentService is the service provided by components (clients).
+
+service EventsClientService {
+ rpc ping(PingRequest) returns (EventResponse);
rpc signal(SignalRequest) returns (EventResponse);
}
diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py
index a4f1df4..274514c 100644
--- a/src/leap/common/events/events_pb2.py
+++ b/src/leap/common/events/events_pb2.py
@@ -1,85 +1,87 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: events.proto
-from google.protobuf import descriptor
-from google.protobuf import message
-from google.protobuf import reflection
-from google.protobuf import service
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import service as _service
from google.protobuf import service_reflection
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
-DESCRIPTOR = descriptor.FileDescriptor(
+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*\xe7\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\x12\x10\n\x0cRAISE_WINDOW\x10\r2\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.EventResponseB\x03\x90\x01\x01')
+ 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\"l\n\x11UnregisterRequest\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\"\r\n\x0bPingRequest\"\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*\xe7\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\x12\x10\n\x0cRAISE_WINDOW\x10\r2\xdd\x02\n\x13\x45ventsServerService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12V\n\nunregister\x12%.leap.common.events.UnregisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2\xb1\x01\n\x13\x45ventsClientService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01')
-_EVENT = descriptor.EnumDescriptor(
+_EVENT = _descriptor.EnumDescriptor(
name='Event',
full_name='leap.common.events.Event',
filename=None,
file=DESCRIPTOR,
values=[
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='CLIENT_SESSION_ID', index=0, number=1,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='CLIENT_UID', index=1, number=2,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_CREATING_KEYS', index=2, number=3,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_UPLOADING_KEYS', index=4, number=5,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='UPDATER_NEW_UPDATES', index=10, number=11,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='UPDATER_DONE_UPDATING', index=11, number=12,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='RAISE_WINDOW', index=12, number=13,
options=None,
type=None),
],
containing_type=None,
options=None,
- serialized_start=432,
- serialized_end=791,
+ serialized_start=557,
+ serialized_end=916,
)
-
+Event = enum_type_wrapper.EnumTypeWrapper(_EVENT)
CLIENT_SESSION_ID = 1
CLIENT_UID = 2
SOLEDAD_CREATING_KEYS = 3
@@ -95,240 +97,349 @@ UPDATER_DONE_UPDATING = 12
RAISE_WINDOW = 13
-_EVENTRESPONSE_STATUS = descriptor.EnumDescriptor(
+_EVENTRESPONSE_STATUS = _descriptor.EnumDescriptor(
name='Status',
full_name='leap.common.events.EventResponse.Status',
filename=None,
file=DESCRIPTOR,
values=[
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='OK', index=0, number=1,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='UNAUTH', index=1, number=2,
options=None,
type=None),
- descriptor.EnumValueDescriptor(
+ _descriptor.EnumValueDescriptor(
name='ERROR', index=2, number=3,
options=None,
type=None),
],
containing_type=None,
options=None,
- serialized_start=390,
- serialized_end=429,
+ serialized_start=515,
+ serialized_end=554,
)
-_SIGNALREQUEST = descriptor.Descriptor(
+_SIGNALREQUEST = _descriptor.Descriptor(
name='SignalRequest',
full_name='leap.common.events.SignalRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
- descriptor.FieldDescriptor(
+ _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,
+ 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,
+)
+
+
+_UNREGISTERREQUEST = _descriptor.Descriptor(
+ name='UnregisterRequest',
+ full_name='leap.common.events.UnregisterRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='event', full_name='leap.common.events.UnregisterRequest.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.UnregisterRequest.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.UnregisterRequest.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.UnregisterRequest.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=298,
+ serialized_end=406,
)
-_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,
+_PINGREQUEST = _descriptor.Descriptor(
+ name='PingRequest',
+ full_name='leap.common.events.PingRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=408,
+ serialized_end=421,
)
-_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,
+_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=424,
+ serialized_end=554,
)
_SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT
_REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT
+_UNREGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT
_EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS
-_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE;
+_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE
DESCRIPTOR.message_types_by_name['SignalRequest'] = _SIGNALREQUEST
DESCRIPTOR.message_types_by_name['RegisterRequest'] = _REGISTERREQUEST
+DESCRIPTOR.message_types_by_name['UnregisterRequest'] = _UNREGISTERREQUEST
+DESCRIPTOR.message_types_by_name['PingRequest'] = _PINGREQUEST
DESCRIPTOR.message_types_by_name['EventResponse'] = _EVENTRESPONSE
-class SignalRequest(message.Message):
- __metaclass__ = reflection.GeneratedProtocolMessageType
+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
+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
+class UnregisterRequest(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _UNREGISTERREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.UnregisterRequest)
+
+
+class PingRequest(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _PINGREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.PingRequest)
+
+
+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=794,
- serialized_end=979,
- methods=[
- descriptor.MethodDescriptor(
- name='register',
- full_name='leap.common.events.EventsServerService.register',
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(
+ descriptor_pb2.FileOptions(), '\220\001\001')
+
+_EVENTSSERVERSERVICE = _descriptor.ServiceDescriptor(
+ name='EventsServerService',
+ full_name='leap.common.events.EventsServerService',
+ file=DESCRIPTOR,
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,
- ),
-])
+ serialized_start=919,
+ serialized_end=1268,
+ methods=[
+ _descriptor.MethodDescriptor(
+ name='ping',
+ full_name='leap.common.events.EventsServerService.ping',
+ index=0,
+ containing_service=None,
+ input_type=_PINGREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='register',
+ full_name='leap.common.events.EventsServerService.register',
+ index=1,
+ containing_service=None,
+ input_type=_REGISTERREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='unregister',
+ full_name='leap.common.events.EventsServerService.unregister',
+ index=2,
+ containing_service=None,
+ input_type=_UNREGISTERREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsServerService.signal',
+ index=3,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
-class EventsServerService(service.Service):
+class EventsServerService(_service.Service):
__metaclass__ = service_reflection.GeneratedServiceType
DESCRIPTOR = _EVENTSSERVERSERVICE
@@ -338,34 +449,43 @@ class EventsServerService_Stub(EventsServerService):
DESCRIPTOR = _EVENTSSERVERSERVICE
-_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor(
- name='EventsComponentService',
- full_name='leap.common.events.EventsComponentService',
- file=DESCRIPTOR,
- index=1,
- options=None,
- serialized_start=981,
- serialized_end=1085,
- methods=[
- descriptor.MethodDescriptor(
- name='signal',
- full_name='leap.common.events.EventsComponentService.signal',
- index=0,
- containing_service=None,
- input_type=_SIGNALREQUEST,
- output_type=_EVENTRESPONSE,
+_EVENTSCLIENTSERVICE = _descriptor.ServiceDescriptor(
+ name='EventsClientService',
+ full_name='leap.common.events.EventsClientService',
+ file=DESCRIPTOR,
+ index=1,
options=None,
- ),
-])
+ serialized_start=1271,
+ serialized_end=1448,
+ methods=[
+ _descriptor.MethodDescriptor(
+ name='ping',
+ full_name='leap.common.events.EventsClientService.ping',
+ index=0,
+ containing_service=None,
+ input_type=_PINGREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsClientService.signal',
+ index=1,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
-class EventsComponentService(service.Service):
+class EventsClientService(_service.Service):
__metaclass__ = service_reflection.GeneratedServiceType
- DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+ DESCRIPTOR = _EVENTSCLIENTSERVICE
-class EventsComponentService_Stub(EventsComponentService):
+class EventsClientService_Stub(EventsClientService):
__metaclass__ = service_reflection.GeneratedServiceStubType
- DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+ DESCRIPTOR = _EVENTSCLIENTSERVICE
# @@protoc_insertion_point(module_scope)
diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py
index 1f3a874..59f3454 100644
--- a/src/leap/common/events/server.py
+++ b/src/leap/common/events/server.py
@@ -17,12 +17,12 @@
"""
A server for the events mechanism.
-A server can receive different kinds of requests from components:
+A server can receive different kinds of requests from clients:
- 1. Registration request: store component port number to be notified when
+ 1. Registration request: store client port number to be notified when
a specific signal arrives.
- 2. Signal request: redistribute the signal to registered components.
+ 2. Signal request: redistribute the signal to registered clients.
"""
import logging
import socket
@@ -40,12 +40,19 @@ logger = logging.getLogger(__name__)
SERVER_PORT = 8090
-# the `registered_components` dictionary below should have the following
+# the `registered_clients` dictionary below should have the following
# format:
#
# { event_signal: [ port, ... ], ... }
#
-registered_components = {}
+registered_clients = {}
+
+
+class PortAlreadyTaken(Exception):
+ """
+ Raised when trying to open a server in a port that is already taken.
+ """
+ pass
def ensure_server(port=SERVER_PORT):
@@ -60,71 +67,150 @@ def ensure_server(port=SERVER_PORT):
:return: the daemon instance or nothing
:rtype: EventsServerDaemon or None
+
+ :raise PortAlreadyTaken: Raised if C{port} is already taken by something
+ that is not an events server.
"""
try:
+ # check if port is available
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
+ # port is taken, check if there's a server running there
+ response = ping(port)
+ if response is not None and response.status == proto.EventResponse.OK:
+ logger.info('A server is already running on port %d.', port)
+ return None
+ # port is taken, and not by an events server
+ logger.info('Port %d is taken by something not an events server.', port)
+ raise PortAlreadyTaken(port)
except socket.error:
+ # port is available, run a server
logger.info('Launching server on port %d.', port)
return EventsServerDaemon.ensure(port)
+def ping(port=SERVER_PORT, reqcbk=None, timeout=1000):
+ """
+ Ping the server.
+
+ :param port: the port in which server should be listening
+ :type port: int
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function(proto.PingRequest, proto.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.PingRequest()
+ service = RpcService(
+ proto.EventsServerService_Stub,
+ port,
+ 'localhost')
+ logger.info("Pinging server in port %d..." % port)
+ return service.ping(request, callback=reqcbk, timeout=timeout)
+
+
class EventsServerService(proto.EventsServerService):
"""
- Service for receiving events in components.
+ Service for receiving events in clients.
"""
def register(self, controller, request, done):
"""
- Register a component port to be signaled when specific events come in.
+ Register a client 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
+ :param request: the request received from the client
: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)[:40])
- # add component port to signal list
- if request.event not in registered_components:
- registered_components[request.event] = set([])
- registered_components[request.event].add(request.port)
- # send response back to component
+ # add client port to signal list
+ if request.event not in registered_clients:
+ registered_clients[request.event] = set([])
+ registered_clients[request.event].add(request.port)
+ # send response back to client
logger.debug('sending response back')
response = proto.EventResponse()
response.status = proto.EventResponse.OK
done.run(response)
+ def unregister(self, controller, request, done):
+ """
+ Unregister a client port so it will not 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 client
+ :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 unregistration request: %s..." % str(request)[:40])
+ # remove client port from signal list
+ response = proto.EventResponse()
+ if request.event in registered_clients:
+ try:
+ registered_clients[request.event].remove(request.port)
+ response.status = proto.EventResponse.OK
+ except KeyError:
+ response.status = proto.EventsResponse.ERROR
+ response.result = 'Port %d not registered.' % request.port
+ # send response back to client
+ logger.debug('sending response back')
+ done.run(response)
+
def signal(self, controller, request, done):
"""
- Perform an RPC call to signal all components registered to receive a
+ Perform an RPC call to signal all clients 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
+ :param request: the request received from the client
: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)[:40])
- # send signal to all registered components
+ logger.info('Received signal from client: %s...', str(request)[:40])
+ # send signal to all registered clients
# TODO: verify signal auth
- if request.event in registered_components:
- for port in registered_components[request.event]:
+ if request.event in registered_clients:
+ for port in registered_clients[request.event]:
def callback(req, resp):
logger.info("Signal received by " + str(port))
- service = RpcService(proto.EventsComponentService_Stub,
+ service = RpcService(proto.EventsClientService_Stub,
port, 'localhost')
service.signal(request, callback=callback)
- # send response back to component
+ # send response back to client
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+ def ping(self, controller, request, done):
+ """
+ Reply to a ping request.
+
+ :param controller: used to mediate a single method call
+ :type controller: protobuf.socketrpc.controller.SocketRpcController
+ :param request: the request received from the client
+ :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 ping request, sending response.")
response = proto.EventResponse()
response.status = proto.EventResponse.OK
done.run(response)
diff --git a/src/leap/common/testing/basetest.py b/src/leap/common/testing/basetest.py
index 8890bf9..54826d5 100644
--- a/src/leap/common/testing/basetest.py
+++ b/src/leap/common/testing/basetest.py
@@ -68,7 +68,8 @@ class BaseLeapTest(unittest.TestCase):
# safety check! please do not wipe my home...
# XXX needs to adapt to non-linuces
leap_assert(
- cls.tempdir.startswith('/tmp/leap_tests-'),
+ cls.tempdir.startswith('/tmp/leap_tests-') or
+ cls.tempdir.startswith('/var/folder'),
"beware! tried to remove a dir which does not "
"live in temporal folder!")
shutil.rmtree(cls.tempdir)
diff --git a/src/leap/common/tests/test_check.py b/src/leap/common/tests/test_check.py
new file mode 100644
index 0000000..6ce8493
--- /dev/null
+++ b/src/leap/common/tests/test_check.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# test_check.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/>.
+"""
+Tests for:
+ * leap/common/check.py
+"""
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
+import mock
+
+from leap.common import check
+
+class CheckTests(unittest.TestCase):
+ def test_raises_on_false_condition(self):
+ with self.assertRaises(AssertionError):
+ check.leap_assert(False, "Condition")
+
+ def test_raises_on_none_condition(self):
+ with self.assertRaises(AssertionError):
+ check.leap_assert(None, "Condition")
+
+ def test_suceeds_with_good_condition(self):
+ check.leap_assert(True, "")
+
+ def test_raises_on_bad_type(self):
+ with self.assertRaises(AssertionError):
+ check.leap_assert_type(42, str)
+
+ def test_succeeds_on_good_type(self):
+ check.leap_assert_type(42, int)
+
+ @mock.patch("traceback.extract_stack", mock.MagicMock(return_value=None))
+ def test_does_not_raise_on_bug(self):
+ with self.assertRaises(AssertionError):
+ check.leap_assert(False, "")
diff --git a/src/leap/common/tests/test_crypto.py b/src/leap/common/tests/test_crypto.py
deleted file mode 100644
index ae7dc71..0000000
--- a/src/leap/common/tests/test_crypto.py
+++ /dev/null
@@ -1,88 +0,0 @@
-## -*- coding: utf-8 -*-
-# test_crypto.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/>.
-
-
-"""
-Tests for the crypto submodule.
-"""
-
-
-import os
-import binascii
-
-
-from leap.common.testing.basetest import BaseLeapTest
-from leap.common import crypto
-from Crypto import Random
-
-
-class CryptoTestCase(BaseLeapTest):
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_encrypt_decrypt_sym(self):
- # generate 256-bit key
- key = Random.new().read(32)
- iv, cyphertext = crypto.encrypt_sym(
- 'data', key,
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertTrue(cyphertext is not None)
- self.assertTrue(cyphertext != '')
- self.assertTrue(cyphertext != 'data')
- plaintext = crypto.decrypt_sym(
- cyphertext, key, iv=iv,
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertEqual('data', plaintext)
-
- def test_decrypt_with_wrong_iv_fails(self):
- key = Random.new().read(32)
- iv, cyphertext = crypto.encrypt_sym(
- 'data', key,
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertTrue(cyphertext is not None)
- self.assertTrue(cyphertext != '')
- self.assertTrue(cyphertext != 'data')
- # get a different iv by changing the first byte
- rawiv = binascii.a2b_base64(iv)
- wrongiv = rawiv
- while wrongiv == rawiv:
- wrongiv = os.urandom(1) + rawiv[1:]
- plaintext = crypto.decrypt_sym(
- cyphertext, key, iv=binascii.b2a_base64(wrongiv),
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertNotEqual('data', plaintext)
-
- def test_decrypt_with_wrong_key_fails(self):
- key = Random.new().read(32)
- iv, cyphertext = crypto.encrypt_sym(
- 'data', key,
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertTrue(cyphertext is not None)
- self.assertTrue(cyphertext != '')
- self.assertTrue(cyphertext != 'data')
- wrongkey = Random.new().read(32) # 256-bits key
- # ensure keys are different in case we are extremely lucky
- while wrongkey == key:
- wrongkey = Random.new().read(32)
- plaintext = crypto.decrypt_sym(
- cyphertext, wrongkey, iv=iv,
- method=crypto.EncryptionMethods.AES_256_CTR)
- self.assertNotEqual('data', plaintext)
diff --git a/src/leap/common/tests/test_events.py b/src/leap/common/tests/test_events.py
index 8c0bd36..0779b2e 100644
--- a/src/leap/common/tests/test_events.py
+++ b/src/leap/common/tests/test_events.py
@@ -18,19 +18,27 @@
import unittest
import sets
import time
+import socket
+import threading
+import random
+
+
+from mock import Mock
from protobuf.socketrpc import RpcService
from leap.common import events
from leap.common.events import (
server,
- component,
+ client,
mac_auth,
)
from leap.common.events.events_pb2 import (
EventsServerService,
EventsServerService_Stub,
+ EventsClientService_Stub,
EventResponse,
SignalRequest,
RegisterRequest,
+ PingRequest,
SOLEDAD_CREATING_KEYS,
CLIENT_UID,
)
@@ -39,11 +47,6 @@ from leap.common.events.events_pb2 import (
port = 8090
received = False
-local_callback_executed = False
-
-
-def callback(request, reponse):
- return True
class EventsTestCase(unittest.TestCase):
@@ -51,7 +54,7 @@ class EventsTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
server.EventsServerDaemon.ensure(8090)
- cls.callbacks = events.component.registered_callbacks
+ cls.callbacks = events.client.registered_callbacks
@classmethod
def tearDownClass(cls):
@@ -62,8 +65,8 @@ class EventsTestCase(unittest.TestCase):
super(EventsTestCase, self).setUp()
def tearDown(self):
- #events.component.registered_callbacks = {}
- server.registered_components = {}
+ #events.client.registered_callbacks = {}
+ server.registered_clients = {}
super(EventsTestCase, self).tearDown()
def test_service_singleton(self):
@@ -76,24 +79,24 @@ class EventsTestCase(unittest.TestCase):
self.assertEqual(service1, service2,
"Can't get singleton class for service.")
- def test_component_register(self):
+ def test_client_register(self):
"""
- Ensure components can register callbacks.
+ Ensure clients can register callbacks.
"""
self.assertTrue(1 not in self.callbacks,
'There should should be no callback for this signal.')
events.register(1, lambda x: True)
self.assertTrue(1 in self.callbacks,
- 'Could not register signal in local component.')
+ 'Could not register signal in local client.')
events.register(2, lambda x: True)
self.assertTrue(1 in self.callbacks,
- 'Could not register signal in local component.')
+ 'Could not register signal in local client.')
self.assertTrue(2 in self.callbacks,
- 'Could not register signal in local component.')
+ 'Could not register signal in local client.')
def test_register_signal_replace(self):
"""
- Make sure components can replace already registered callbacks.
+ Make sure clients can replace already registered callbacks.
"""
sig = 3
cbk = lambda x: True
@@ -120,21 +123,32 @@ class EventsTestCase(unittest.TestCase):
response = service.signal(request, timeout=1000)
self.assertEqual(EventResponse.OK, response.status,
'Wrong response status.')
- # test asynch
- def local_callback(request, response):
- global local_callback_executed
- local_callback_executed = True
+ def test_signal_executes_callback(self):
+ """
+ Ensure callback is executed upon receiving signal.
+ """
+ sig = CLIENT_UID
+ request = SignalRequest()
+ request.event = sig
+ request.content = 'my signal contents'
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(EventsServerService_Stub, port, 'localhost')
- events.register(sig, local_callback)
- service.signal(request, callback=local_callback)
- time.sleep(0.1)
- self.assertTrue(local_callback_executed,
- 'Local callback did not execute.')
+ # register a callback
+ flag = Mock()
+ events.register(sig, lambda req: flag(req.event))
+ # signal
+ response = service.signal(request)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+ time.sleep(1) # wait for signal to arrive
+ flag.assert_called_once_with(sig)
def test_events_server_service_register(self):
"""
- Ensure the server can register components to be signaled.
+ Ensure the server can register clients to be signaled.
"""
sig = 5
request = RegisterRequest()
@@ -143,42 +157,39 @@ class EventsTestCase(unittest.TestCase):
request.mac_method = mac_auth.MacMethod.MAC_NONE
request.mac = ""
service = RpcService(EventsServerService_Stub, port, 'localhost')
- complist = server.registered_components
+ complist = server.registered_clients
self.assertEqual({}, complist,
'There should be no registered_ports when '
'server has just been created.')
response = service.register(request, timeout=1000)
self.assertTrue(sig in complist, "Signal not registered succesfully.")
self.assertTrue(8091 in complist[sig],
- 'Failed registering component port.')
+ 'Failed registering client port.')
- def test_component_request_register(self):
+ def test_client_request_register(self):
"""
- Ensure components can register themselves with server.
+ Ensure clients can register themselves with server.
"""
sig = 6
- complist = server.registered_components
+ complist = server.registered_clients
self.assertTrue(sig not in complist,
- 'There should be no registered components for this '
+ 'There should be no registered clients for this '
'signal.')
events.register(sig, lambda x: True)
time.sleep(0.1)
- port = component.EventsComponentDaemon.get_instance().get_port()
- self.assertTrue(sig in complist, 'Failed registering component.')
+ port = client.EventsClientDaemon.get_instance().get_port()
+ self.assertTrue(sig in complist, 'Failed registering client.')
self.assertTrue(port in complist[sig],
- 'Failed registering component port.')
+ 'Failed registering client port.')
- def test_component_receives_signal(self):
+ def test_client_receives_signal(self):
"""
- Ensure components can receive signals.
+ Ensure clients can receive signals.
"""
sig = 7
+ flag = Mock()
- def getsig(param=None):
- global received
- received = True
-
- events.register(sig, getsig)
+ events.register(sig, lambda req: flag(req.event))
request = SignalRequest()
request.event = sig
request.content = ""
@@ -188,13 +199,230 @@ class EventsTestCase(unittest.TestCase):
response = service.signal(request, timeout=1000)
self.assertTrue(response is not None, 'Did not receive response.')
time.sleep(0.5)
- self.assertTrue(received, 'Did not receive signal back.')
+ flag.assert_called_once_with(sig)
- def test_component_send_signal(self):
+ def test_client_send_signal(self):
"""
- Ensure components can send signals.
+ Ensure clients can send signals.
"""
sig = 8
response = events.signal(sig)
self.assertTrue(response.status == response.OK,
'Received wrong response status when signaling.')
+
+ def test_client_unregister_all(self):
+ """
+ Test that the client can unregister all events for one signal.
+ """
+ sig = CLIENT_UID
+ complist = server.registered_clients
+ events.register(sig, lambda x: True)
+ events.register(sig, lambda x: True)
+ time.sleep(0.1)
+ events.unregister(sig)
+ time.sleep(0.1)
+ port = client.EventsClientDaemon.get_instance().get_port()
+ self.assertFalse(bool(complist[sig]))
+ self.assertTrue(port not in complist[sig])
+
+ def test_client_unregister_by_uid(self):
+ """
+ Test that the client can unregister an event by uid.
+ """
+ sig = CLIENT_UID
+ complist = server.registered_clients
+ events.register(sig, lambda x: True, uid='cbkuid')
+ events.register(sig, lambda x: True, uid='cbkuid2')
+ time.sleep(0.1)
+ events.unregister(sig, uid='cbkuid')
+ time.sleep(0.1)
+ port = client.EventsClientDaemon.get_instance().get_port()
+ self.assertTrue(sig in complist)
+ self.assertTrue(len(complist[sig]) == 1)
+ self.assertTrue(
+ client.registered_callbacks[sig].pop()[0] == 'cbkuid2')
+ self.assertTrue(port in complist[sig])
+
+ def test_server_replies_ping(self):
+ """
+ Ensure server replies to a ping.
+ """
+ request = PingRequest()
+ service = RpcService(EventsServerService_Stub, port, 'localhost')
+ response = service.ping(request, timeout=1000)
+ self.assertIsNotNone(response)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_client_replies_ping(self):
+ """
+ Ensure clients reply to a ping.
+ """
+ daemon = client.ensure_client_daemon()
+ port = daemon.get_port()
+ request = PingRequest()
+ service = RpcService(EventsClientService_Stub, port, 'localhost')
+ response = service.ping(request, timeout=1000)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_server_ping(self):
+ """
+ Ensure the function from server module pings correctly.
+ """
+ response = server.ping()
+ self.assertIsNotNone(response)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_client_ping(self):
+ """
+ Ensure the function from client module pings correctly.
+ """
+ daemon = client.ensure_client_daemon()
+ response = client.ping(daemon.get_port())
+ self.assertIsNotNone(response)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_module_ping_server(self):
+ """
+ Ensure the function from main module pings server correctly.
+ """
+ response = events.ping_server()
+ self.assertIsNotNone(response)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_module_ping_client(self):
+ """
+ Ensure the function from main module pings clients correctly.
+ """
+ daemon = client.ensure_client_daemon()
+ response = events.ping_client(daemon.get_port())
+ self.assertIsNotNone(response)
+ self.assertEqual(EventResponse.OK, response.status,
+ 'Wrong response status.')
+
+ def test_ensure_server_raises_if_port_taken(self):
+ """
+ Verify that server raises an exception if port is already taken.
+ """
+ # get a random free port
+ while True:
+ port = random.randint(1024, 65535)
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', port))
+ s.close()
+ except:
+ break
+
+ class PortBlocker(threading.Thread):
+
+ def run(self):
+ conns = 0
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.bind(('localhost', port))
+ s.setblocking(1)
+ s.listen(1)
+ while conns < 2: # blocks until rece
+ conns += 1
+ s.accept()
+ s.close()
+
+ # block the port
+ taker = PortBlocker()
+ taker.start()
+ time.sleep(1) # wait for thread to start.
+ self.assertRaises(
+ server.PortAlreadyTaken, server.ensure_server, port)
+
+ def test_async_register(self):
+ """
+ Test asynchronous registering of callbacks.
+ """
+ flag = Mock()
+
+ # executed after async register, when response is received from server
+ def reqcbk(request, response):
+ flag(request.event, response.status)
+
+ # callback registered by application
+ def callback(request):
+ pass
+
+ # passing a callback as reqcbk param makes the call asynchronous
+ result = events.register(CLIENT_UID, callback, reqcbk=reqcbk)
+ self.assertIsNone(result)
+ events.signal(CLIENT_UID)
+ time.sleep(1) # wait for signal to arrive from server
+ flag.assert_called_once_with(CLIENT_UID, EventResponse.OK)
+
+ def test_async_signal(self):
+ """
+ Test asynchronous registering of callbacks.
+ """
+ flag = Mock()
+
+ # executed after async signal, when response is received from server
+ def reqcbk(request, response):
+ flag(request.event, response.status)
+
+ # passing a callback as reqcbk param makes the call asynchronous
+ result = events.signal(CLIENT_UID, reqcbk=reqcbk)
+ self.assertIsNone(result)
+ time.sleep(1) # wait for signal to arrive from server
+ flag.assert_called_once_with(CLIENT_UID, EventResponse.OK)
+
+ def test_async_unregister(self):
+ """
+ Test asynchronous unregistering of callbacks.
+ """
+ flag = Mock()
+
+ # executed after async signal, when response is received from server
+ def reqcbk(request, response):
+ flag(request.event, response.status)
+
+ # callback registered by application
+ def callback(request):
+ pass
+
+ # passing a callback as reqcbk param makes the call asynchronous
+ events.register(CLIENT_UID, callback)
+ result = events.unregister(CLIENT_UID, reqcbk=reqcbk)
+ self.assertIsNone(result)
+ time.sleep(1) # wait for signal to arrive from server
+ flag.assert_called_once_with(CLIENT_UID, EventResponse.OK)
+
+ def test_async_ping_server(self):
+ """
+ Test asynchronous pinging of server.
+ """
+ flag = Mock()
+
+ # executed after async signal, when response is received from server
+ def reqcbk(request, response):
+ flag(response.status)
+
+ result = events.ping_server(reqcbk=reqcbk)
+ self.assertIsNone(result)
+ time.sleep(1) # wait for response to arrive from server.
+ flag.assert_called_once_with(EventResponse.OK)
+
+ def test_async_ping_client(self):
+ """
+ Test asynchronous pinging of client.
+ """
+ flag = Mock()
+
+ # executed after async signal, when response is received from server
+ def reqcbk(request, response):
+ flag(response.status)
+
+ daemon = client.ensure_client_daemon()
+ result = events.ping_client(daemon.get_port(), reqcbk=reqcbk)
+ self.assertIsNone(result)
+ time.sleep(1) # wait for response to arrive from server.
+ flag.assert_called_once_with(EventResponse.OK)