diff options
| author | Micah Anderson <micah@riseup.net> | 2013-08-13 14:25:57 -0400 | 
|---|---|---|
| committer | Micah Anderson <micah@riseup.net> | 2013-08-13 14:27:01 -0400 | 
| commit | ddff9c2dda51fd28dcdc8e2db998d7254f69a41b (patch) | |
| tree | b000723d9c7e54f0ac163e54f8d0d6fcd829209a | |
| parent | cc8dd1e7f3d64ae6d7d69ed08a4346bfabfe7b22 (diff) | |
| parent | 0e721b1b47c3b94f6d4d6709e34b6b816f9fd810 (diff) | |
Merge tag '0.3.0' into debian
Tag leap.common version 0.3.0
| -rw-r--r-- | CHANGELOG | 37 | ||||
| -rw-r--r-- | debian/changelog | 6 | ||||
| -rw-r--r-- | setup.py | 2 | ||||
| -rw-r--r-- | src/leap/common/__init__.py | 2 | ||||
| -rw-r--r-- | src/leap/common/check.py | 26 | ||||
| -rw-r--r-- | src/leap/common/config/baseconfig.py | 65 | ||||
| -rw-r--r-- | src/leap/common/config/prefixers.py | 2 | ||||
| -rw-r--r-- | src/leap/common/config/tests/test_baseconfig.py | 268 | ||||
| -rw-r--r-- | src/leap/common/crypto.py | 114 | ||||
| -rw-r--r-- | src/leap/common/events/README.rst | 12 | ||||
| -rw-r--r-- | src/leap/common/events/__init__.py | 128 | ||||
| -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.py | 2 | ||||
| -rw-r--r-- | src/leap/common/events/events.proto | 44 | ||||
| -rw-r--r-- | src/leap/common/events/events_pb2.py | 550 | ||||
| -rw-r--r-- | src/leap/common/events/server.py | 132 | ||||
| -rw-r--r-- | src/leap/common/testing/basetest.py | 3 | ||||
| -rw-r--r-- | src/leap/common/tests/test_check.py | 52 | ||||
| -rw-r--r-- | src/leap/common/tests/test_crypto.py | 88 | ||||
| -rw-r--r-- | src/leap/common/tests/test_events.py | 316 | 
20 files changed, 1450 insertions, 554 deletions
| diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..81c5f96 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,37 @@ +0.3.0 Aug 9: +  o OSX: Fix problem with path prefix not returning the correct +    value. Fixes #3273. +  o Check if schema exists before load a config. Related to #3310. +  o Handle schemas and api versions in base class. Related to #3310. + +0.2.7 Jul 26: +  o Refactor events so components are now called clients. Closes #3246 +  o Add leap_check helper method, to use whenever leap_assert does not +    apply. Related to #3007. + +0.2.6 Jul 12: +  o Improve leap_assert so that it only prints the traceback from the +    leap_assert call up. Closes #2895 +  o Add OSX temp directories to the basetests class. + +0.2.5 Jun 28: +  o Bugfix: use the provider's default language as default +    string. Also take care (and note) a possible case with a +    problematic provider misconfiguration. Closes #3029. +  o Add data files to setup and manifest (certificates for tests) +  o Allow absolute paths in baseconfig.load +  o Fix deprecation warnings +  o Fix attempt to fetch private keys from server. +  o Fix missing imports +  o Add possibility of unregistering callbacks for a signal. +  o Add a mechanism for events signaling between components. +  o Prioritize the path_extension in the which method so it finds our +    bundled app before the system one, if any. +  o Move the Key Manager to leap client repository. +  o Move symmetric encryption code to leap.soledad. +  o Refactor opengpg utility functions implementation so it uses a +    context manager. +  o Add OpenPGP sign/verify +  o Add RAISE_WINDOW event +  o Add AES-256 (CTR mode) encrypting/decrypting functions using +    PyCrypto. diff --git a/debian/changelog b/debian/changelog index 098528e..20f0576 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +leap-common (0.3.0) unstable; urgency=low + +  * Update to 0.3.0 + + -- Micah Anderson <micah@debian.org>  Tue, 13 Aug 2013 14:26:37 -0400 +  leap-common (0.2.5) unstable; urgency=low    * Upgrade to 0.2.5 @@ -56,7 +56,7 @@ setup(      name='leap.common',      # If you change version, do it also in      # src/leap/common/__init__.py -    version='0.2.5', +    version='0.3.0',      url='https://leap.se/',      license='GPLv3+',      author='The LEAP Encryption Access Project', 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) | 
