summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-06-28 14:55:33 -0300
committerTomás Touceda <chiiph@leap.se>2013-06-28 14:55:33 -0300
commitd071efe20a285e579111f568e2ff994a6171249f (patch)
tree824ea959c02c8ed3af36c0cec8421e0748d311cc
parentb966eec96fd3621908baab8697de8e2ed61ba136 (diff)
parenta0f78f9d708cc6fe686ee5860cdc34909391b14b (diff)
Merge branch 'release-0.2.5'
-rw-r--r--.gitignore6
-rw-r--r--CHANGELOG21
-rw-r--r--MANIFEST.in1
-rw-r--r--README.rst19
-rw-r--r--changes/feature_improve_which2
-rw-r--r--pkg/requirements-testing.pip1
-rw-r--r--pkg/requirements.pip10
-rw-r--r--setup.py47
-rw-r--r--src/leap/common/__init__.py7
-rw-r--r--src/leap/common/certs.py48
-rw-r--r--src/leap/common/check.py16
-rw-r--r--src/leap/common/config/__init__.py0
-rw-r--r--src/leap/common/config/baseconfig.py207
-rw-r--r--src/leap/common/config/pluggableconfig.py475
-rw-r--r--src/leap/common/config/prefixers.py132
-rw-r--r--src/leap/common/events/Makefile3
-rw-r--r--src/leap/common/events/README.rst53
-rw-r--r--src/leap/common/events/__init__.py126
-rw-r--r--src/leap/common/events/component.py290
-rw-r--r--src/leap/common/events/daemon.py208
-rw-r--r--src/leap/common/events/events.proto79
-rw-r--r--src/leap/common/events/events_pb2.py444
-rw-r--r--src/leap/common/events/mac_auth.py31
-rw-r--r--src/leap/common/events/server.py176
-rw-r--r--src/leap/common/events/service.py114
-rw-r--r--src/leap/common/events/signal.proto57
-rw-r--r--src/leap/common/events/signal_pb2.py250
-rw-r--r--src/leap/common/events/test_events.py88
-rw-r--r--src/leap/common/files.py26
-rw-r--r--src/leap/common/testing/basetest.py16
-rw-r--r--src/leap/common/testing/test_basetest.py6
-rw-r--r--src/leap/common/tests/__init__.py0
-rw-r--r--src/leap/common/tests/test_events.py233
33 files changed, 2589 insertions, 603 deletions
diff --git a/.gitignore b/.gitignore
index ff0025c..e0d60cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
-*.pyc
+*.pyc
+*.egg
*.egg-info
+*.swp
+*.swo
dist/
+build/
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..5f9cb69
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,21 @@
+0.2.2 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/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..84a01ef
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include src/leap/common/testing/*.pem
diff --git a/README.rst b/README.rst
index 5ea7bda..aeb871f 100644
--- a/README.rst
+++ b/README.rst
@@ -1,3 +1,22 @@
leap.common
===========
+
+.. image:: https://pypip.in/v/leap.common/badge.png
+ :target: https://crate.io/packages/leap.common
+
A collection of shared utils used by the several python LEAP subprojects.
+
+* leap.common.cert
+* leap.common.checks
+* leap.common.config
+* leap.common.events
+* leap.common.files
+* leap.common.testing
+
+Library dependencies
+--------------------
+* ``protobuf-compiler``
+
+Python dependencies
+-------------------
+* See ``pkg/requirements.pip``
diff --git a/changes/feature_improve_which b/changes/feature_improve_which
deleted file mode 100644
index d1d1fb5..0000000
--- a/changes/feature_improve_which
+++ /dev/null
@@ -1,2 +0,0 @@
- o Prioritize the path_extension in the which method so it finds our bundled
- app before the system one, if any.
diff --git a/pkg/requirements-testing.pip b/pkg/requirements-testing.pip
new file mode 100644
index 0000000..932a895
--- /dev/null
+++ b/pkg/requirements-testing.pip
@@ -0,0 +1 @@
+mock
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
new file mode 100644
index 0000000..141c325
--- /dev/null
+++ b/pkg/requirements.pip
@@ -0,0 +1,10 @@
+jsonschema<=0.8
+pyxdg
+protobuf
+pyopenssl
+python-dateutil
+autopep8
+python-gnupg
+PyCrypto
+
+https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz
diff --git a/setup.py b/setup.py
index b29c352..bdf97c5 100644
--- a/setup.py
+++ b/setup.py
@@ -19,25 +19,62 @@ setup file for leap.common
"""
from setuptools import setup, find_packages
+# XXX parse pkg/requirements.pip
requirements = [
+ "jsonschema",
+ "pyxdg",
+ 'protobuf',
+ 'protobuf.socketrpc',
+ "PyOpenSSL",
+ "python-dateutil",
+ "PyCrypto",
]
-# XXX add classifiers, docs
+#dependency_links = [
+ #"https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc"
+#]
+
+tests_requirements = [
+ 'mock',
+]
+
+trove_classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ ("License :: OSI Approved :: GNU General "
+ "Public License v3 or later (GPLv3+)"),
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Topic :: Communications",
+ "Topic :: Security",
+ "Topic :: Utilities"
+]
setup(
name='leap.common',
- version='0.2.0-dev',
+ # If you change version, do it also in
+ # src/leap/common/__init__.py
+ version='0.2.5',
url='https://leap.se/',
license='GPLv3+',
author='The LEAP Encryption Access Project',
author_email='info@leap.se',
- description='Common files used by the LEAP Client project.',
+ description='Common files used by the LEAP project.',
long_description=(
"Common files used by the LEAP Client project."
),
+ classifiers=trove_classifiers,
namespace_packages=["leap"],
package_dir={'': 'src'},
+ # For now, we do not exclude tests because of the circular dependency
+ # between leap.common and leap.soledad.
+ #packages=find_packages('src', exclude=['leap.common.tests']),
packages=find_packages('src'),
- #test_suite='leap.common.tests',
- #install_requires=requirements,
+ test_suite='leap.common.tests',
+ install_requires=requirements,
+ #dependency_links=dependency_links,
+ tests_require=tests_requirements,
+ include_package_data=True
)
diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py
index 9467c46..2b30715 100644
--- a/src/leap/common/__init__.py
+++ b/src/leap/common/__init__.py
@@ -3,6 +3,7 @@ import logging
from leap.common import certs
from leap.common import check
from leap.common import files
+from leap.common import events
logger = logging.getLogger(__name__)
@@ -10,9 +11,9 @@ try:
import pygeoip
HAS_GEOIP = True
except ImportError:
- logger.debug('PyGeoIP not found. Disabled Geo support.')
+ #logger.debug('PyGeoIP not found. Disabled Geo support.')
HAS_GEOIP = False
-__all__ = ["certs", "check", "files"]
+__all__ = ["certs", "check", "files", "events"]
-__version__ = "0.2.0-dev"
+__version__ = "0.2.5"
diff --git a/src/leap/common/certs.py b/src/leap/common/certs.py
index 4cb70dd..4fe563b 100644
--- a/src/leap/common/certs.py
+++ b/src/leap/common/certs.py
@@ -35,10 +35,10 @@ def get_cert_from_string(string):
"""
Returns the x509 from the contents of this string
- @param string: certificate contents as downloaded
- @type string: str
+ :param string: certificate contents as downloaded
+ :type string: str
- @return: x509 or None
+ :return: x509 or None
"""
leap_assert(string, "We need something to load")
@@ -55,10 +55,10 @@ def get_privatekey_from_string(string):
"""
Returns the private key from the contents of this string
- @param string: private key contents as downloaded
- @type string: str
+ :param string: private key contents as downloaded
+ :type string: str
- @return: private key or None
+ :return: private key or None
"""
leap_assert(string, "We need something to load")
@@ -75,12 +75,12 @@ def get_digest(cert_data, method):
"""
Returns the digest for the cert_data using the method specified
- @param cert_data: certificate data in string form
- @type cert_data: str
- @param method: method to be used for digest
- @type method: str
+ :param cert_data: certificate data in string form
+ :type cert_data: str
+ :param method: method to be used for digest
+ :type method: str
- @rtype: str
+ :rtype: str
"""
x509 = get_cert_from_string(cert_data)
digest = x509.digest(method).replace(":", "").lower()
@@ -93,10 +93,10 @@ def can_load_cert_and_pkey(string):
Loads certificate and private key from a buffer, returns True if
everything went well, False otherwise
- @param string: buffer containing the cert and private key
- @type string: str or any kind of buffer
+ :param string: buffer containing the cert and private key
+ :type string: str or any kind of buffer
- @rtype: bool
+ :rtype: bool
"""
can_load = True
@@ -118,10 +118,10 @@ def is_valid_pemfile(cert):
"""
Checks that the passed string is a valid pem certificate
- @param cert: String containing pem content
- @type cert: str
+ :param cert: String containing pem content
+ :type cert: str
- @rtype: bool
+ :rtype: bool
"""
leap_assert(cert, "We need a cert to load")
@@ -132,10 +132,10 @@ def get_cert_time_boundaries(certfile):
"""
Returns the time boundaries for the certificate saved in certfile
- @param certfile: path to certificate
- @type certfile: str
+ :param certfile: path to certificate
+ :type certfile: str
- @rtype: tuple (from, to)
+ :rtype: tuple (from, to)
"""
cert = get_cert_from_string(certfile)
leap_assert(cert, 'There was a problem loading the certificate')
@@ -151,11 +151,11 @@ def should_redownload(certfile, now=time.gmtime):
"""
Returns True if any of the checks don't pass, False otherwise
- @param certfile: path to certificate
- @type certfile: str
- @param now: current date function, ONLY USED FOR TESTING
+ :param certfile: path to certificate
+ :type certfile: str
+ :param now: current date function, ONLY USED FOR TESTING
- @rtype: bool
+ :rtype: bool
"""
exists = os.path.isfile(certfile)
diff --git a/src/leap/common/check.py b/src/leap/common/check.py
index 359673b..a2d39a6 100644
--- a/src/leap/common/check.py
+++ b/src/leap/common/check.py
@@ -31,10 +31,10 @@ def leap_assert(condition, message=""):
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 condition: condition to check
+ :type condition: bool
+ :param message: message to display if the condition isn't met
+ :type message: str
"""
if not condition:
logger.error("Bug: %s" % (message,))
@@ -51,10 +51,10 @@ def leap_assert_type(var, expectedType):
"""
Helper assert check for a variable's expected type
- @param var: variable to check
- @type var: any
- @param expectedType: type to check agains
- @type expectedType: type
+ :param var: variable to check
+ :type var: any
+ :param expectedType: type to check agains
+ :type expectedType: type
"""
leap_assert(isinstance(var, expectedType),
"Expected type %r instead of %r" %
diff --git a/src/leap/common/config/__init__.py b/src/leap/common/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/leap/common/config/__init__.py
diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py
new file mode 100644
index 0000000..699d734
--- /dev/null
+++ b/src/leap/common/config/baseconfig.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+# 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/>.
+
+"""
+Implements the abstract base class for configuration
+"""
+
+import copy
+import logging
+import functools
+import os
+
+from abc import ABCMeta, abstractmethod
+
+from leap.common.check import leap_assert
+from leap.common.files import mkdir_p
+from leap.common.config.pluggableconfig import PluggableConfig
+from leap.common.config.prefixers import get_platform_prefixer
+
+logger = logging.getLogger(__name__)
+
+
+class BaseConfig:
+ """
+ Abstract base class for any JSON based configuration.
+ """
+
+ __metaclass__ = ABCMeta
+
+ """
+ Standalone is a class wide parameter.
+
+ :param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will
+ return the system
+ default for configuration storage.
+ :type standalone: bool
+ """
+ standalone = False
+
+ def __init__(self):
+ self._data = {}
+ self._config_checker = None
+
+ @abstractmethod
+ def _get_spec(self):
+ """
+ Returns the spec object for the specific configuration.
+ """
+ return None
+
+ def _safe_get_value(self, key):
+ """
+ Tries to return a value only if the config has already been loaded.
+
+ :rtype: depends on the config structure, dict, str, array, int
+ :return: returns the value for the specified key in the config
+ """
+ leap_assert(self._config_checker, "Load the config first")
+ return self._config_checker.config.get(key, None)
+
+ def get_path_prefix(self):
+ """
+ Returns the platform dependant path prefixer
+ """
+ return get_platform_prefixer().get_path_prefix(
+ standalone=self.standalone)
+
+ def loaded(self):
+ """
+ Returns True if the configuration has been already
+ loaded. False otherwise
+ """
+ return self._config_checker is not None
+
+ def save(self, path_list):
+ """
+ Saves the current configuration to disk.
+
+ :param path_list: list of components that form the relative
+ path to configuration. The absolute path
+ will be calculated depending on the platform.
+ :type path_list: list
+
+ :return: True if saved to disk correctly, False otherwise
+ """
+ config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1]))
+ mkdir_p(config_path)
+
+ try:
+ self._config_checker.serialize(os.path.join(config_path,
+ path_list[-1]))
+ except Exception as e:
+ logger.warning("%s" % (e,))
+ raise
+ return True
+
+ def load(self, path="", data=None, mtime=None, relative=True):
+ """
+ Loads the configuration from disk.
+
+ :param path: if relative=True, this is a relative path
+ to configuration. The absolute path
+ will be calculated depending on the platform
+ :type path: str
+
+ :param relative: if True, path is relative. If False, it's absolute.
+ :type relative: bool
+
+ :return: True if loaded from disk correctly, False otherwise
+ :rtype: bool
+ """
+
+ if relative is True:
+ config_path = os.path.join(
+ self.get_path_prefix(), path)
+ else:
+ config_path = path
+
+ self._config_checker = PluggableConfig(format="json")
+ self._config_checker.options = copy.deepcopy(self._get_spec())
+
+ try:
+ if data is None:
+ self._config_checker.load(fromfile=config_path, mtime=mtime)
+ else:
+ self._config_checker.load(data, mtime=mtime)
+ except Exception as e:
+ logger.error("Something went wrong while loading " +
+ "the config from %s\n%s" % (config_path, e))
+ self._config_checker = None
+ return False
+ return True
+
+
+class LocalizedKey(object):
+ """
+ Decorator used for keys that are localized in a configuration.
+ """
+
+ def __init__(self, func, **kwargs):
+ self._func = func
+
+ def __call__(self, instance, lang=None):
+ """
+ Tries to return the string for the specified language, otherwise
+ 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)
+ 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.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):
+ """
+ Implement the descriptor protocol to make decorating instance
+ method possible.
+ """
+ # Return a partial function with the first argument is the instance
+ # of the class decorated.
+ return functools.partial(self.__call__, instance)
+
+if __name__ == "__main__":
+ try:
+ config = BaseConfig() # should throw TypeError for _get_spec
+ except Exception as e:
+ assert isinstance(e, TypeError), "Something went wrong"
+ print "Abstract BaseConfig class is working as expected"
diff --git a/src/leap/common/config/pluggableconfig.py b/src/leap/common/config/pluggableconfig.py
new file mode 100644
index 0000000..8535fa6
--- /dev/null
+++ b/src/leap/common/config/pluggableconfig.py
@@ -0,0 +1,475 @@
+# -*- coding: utf-8 -*-
+# pluggableconfig.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/>.
+
+"""
+generic configuration handlers
+"""
+import copy
+import json
+import logging
+import os
+import time
+import urlparse
+
+import jsonschema
+
+#from leap.base.util.translations import LEAPTranslatable
+from leap.common.check import leap_assert
+
+
+logger = logging.getLogger(__name__)
+
+
+__all__ = ['PluggableConfig',
+ 'adaptors',
+ 'types',
+ 'UnknownOptionException',
+ 'MissingValueException',
+ 'ConfigurationProviderException',
+ 'TypeCastException']
+
+# exceptions
+
+
+class UnknownOptionException(Exception):
+ """exception raised when a non-configuration
+ value is present in the configuration"""
+
+
+class MissingValueException(Exception):
+ """exception raised when a required value is missing"""
+
+
+class ConfigurationProviderException(Exception):
+ """exception raised when a configuration provider is missing, etc"""
+
+
+class TypeCastException(Exception):
+ """exception raised when a
+ configuration item cannot be coerced to a type"""
+
+
+class ConfigAdaptor(object):
+ """
+ abstract base class for config adaotors for
+ serialization/deserialization and custom validation
+ and type casting.
+ """
+ def read(self, filename):
+ raise NotImplementedError("abstract base class")
+
+ def write(self, config, filename):
+ with open(filename, 'w') as f:
+ self._write(f, config)
+
+ def _write(self, fp, config):
+ raise NotImplementedError("abstract base class")
+
+ def validate(self, config, schema):
+ raise NotImplementedError("abstract base class")
+
+
+adaptors = {}
+
+
+class JSONSchemaEncoder(json.JSONEncoder):
+ """
+ custom default encoder that
+ casts python objects to json objects for
+ the schema validation
+ """
+ def default(self, obj):
+ if obj is str:
+ return 'string'
+ if obj is unicode:
+ return 'string'
+ if obj is int:
+ return 'integer'
+ if obj is list:
+ return 'array'
+ if obj is dict:
+ return 'object'
+ if obj is bool:
+ return 'boolean'
+
+
+class JSONAdaptor(ConfigAdaptor):
+ indent = 2
+ extensions = ['json']
+
+ def read(self, _from):
+ if isinstance(_from, file):
+ _from_string = _from.read()
+ if isinstance(_from, str):
+ _from_string = _from
+ return json.loads(_from_string)
+
+ def _write(self, fp, config):
+ fp.write(json.dumps(config,
+ indent=self.indent,
+ sort_keys=True))
+
+ def validate(self, config, schema_obj):
+ schema_json = JSONSchemaEncoder().encode(schema_obj)
+ schema = json.loads(schema_json)
+ jsonschema.validate(config, schema)
+
+
+adaptors['json'] = JSONAdaptor()
+
+#
+# Adaptors
+#
+# Allow to apply a predefined set of types to the
+# specs, so it checks the validity of formats and cast it
+# to proper python types.
+
+# TODO:
+# - HTTPS uri
+
+
+class DateType(object):
+ fmt = '%Y-%m-%d'
+
+ def to_python(self, data):
+ return time.strptime(data, self.fmt)
+
+ def get_prep_value(self, data):
+ return time.strftime(self.fmt, data)
+
+
+class TranslatableType(object):
+ """
+ a type that casts to LEAPTranslatable objects.
+ Used for labels we get from providers and stuff.
+ """
+
+ def to_python(self, data):
+ # TODO: add translatable
+ return data # LEAPTranslatable(data)
+
+ # needed? we already have an extended dict...
+ #def get_prep_value(self, data):
+ #return dict(data)
+
+
+class URIType(object):
+
+ def to_python(self, data):
+ parsed = urlparse.urlparse(data)
+ if not parsed.scheme:
+ raise TypeCastException("uri %s has no schema" % data)
+ return parsed.geturl()
+
+ def get_prep_value(self, data):
+ return data
+
+
+class HTTPSURIType(object):
+
+ def to_python(self, data):
+ parsed = urlparse.urlparse(data)
+ if not parsed.scheme:
+ raise TypeCastException("uri %s has no schema" % data)
+ if parsed.scheme != "https":
+ raise TypeCastException(
+ "uri %s does not has "
+ "https schema" % data)
+ return parsed.geturl()
+
+ def get_prep_value(self, data):
+ return data
+
+
+types = {
+ 'date': DateType(),
+ 'uri': URIType(),
+ 'https-uri': HTTPSURIType(),
+ 'translatable': TranslatableType(),
+}
+
+
+class PluggableConfig(object):
+
+ options = {}
+
+ def __init__(self,
+ adaptors=adaptors,
+ types=types,
+ format=None):
+
+ self.config = {}
+ self.adaptors = adaptors
+ self.types = types
+ self._format = format
+ self.mtime = None
+ self.dirty = False
+
+ @property
+ def option_dict(self):
+ if hasattr(self, 'options') and isinstance(self.options, dict):
+ return self.options.get('properties', None)
+
+ def items(self):
+ """
+ act like an iterator
+ """
+ if isinstance(self.option_dict, dict):
+ return self.option_dict.items()
+ return self.options
+
+ def validate(self, config, format=None):
+ """
+ validate config
+ """
+ schema = self.options
+ if format is None:
+ format = self._format
+
+ if format:
+ adaptor = self.get_adaptor(self._format)
+ adaptor.validate(config, schema)
+ else:
+ # we really should make format mandatory...
+ logger.error('no format passed to validate')
+
+ # first round of validation is ok.
+ # now we proceed to cast types if any specified.
+ self.to_python(config)
+
+ def to_python(self, config):
+ """
+ cast types following first type and then format indications.
+ """
+ unseen_options = [i for i in config if i not in self.option_dict]
+ if unseen_options:
+ raise UnknownOptionException(
+ "Unknown options: %s" % ', '.join(unseen_options))
+
+ for key, value in config.items():
+ _type = self.option_dict[key].get('type')
+ if _type is None and 'default' in self.option_dict[key]:
+ _type = type(self.option_dict[key]['default'])
+ if _type is not None:
+ tocast = True
+ if not callable(_type) and isinstance(value, _type):
+ tocast = False
+ if tocast:
+ try:
+ config[key] = _type(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not coerce %s, %s, "
+ "to type %s: %s" % (key, value, _type.__name__, e))
+ _format = self.option_dict[key].get('format', None)
+ _ftype = self.types.get(_format, None)
+ if _ftype:
+ try:
+ config[key] = _ftype.to_python(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not coerce %s, %s, "
+ "to format %s: %s" % (key, value,
+ _ftype.__class__.__name__,
+ e))
+
+ return config
+
+ def prep_value(self, config):
+ """
+ the inverse of to_python method,
+ called just before serialization
+ """
+ for key, value in config.items():
+ _format = self.option_dict[key].get('format', None)
+ _ftype = self.types.get(_format, None)
+ if _ftype and hasattr(_ftype, 'get_prep_value'):
+ try:
+ config[key] = _ftype.get_prep_value(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not serialize %s, %s, "
+ "by format %s: %s" % (key, value,
+ _ftype.__class__.__name__,
+ e))
+ else:
+ config[key] = value
+ return config
+
+ # methods for adding configuration
+
+ def get_default_values(self):
+ """
+ return a config options from configuration defaults
+ """
+ defaults = {}
+ for key, value in self.items():
+ if 'default' in value:
+ defaults[key] = value['default']
+ return copy.deepcopy(defaults)
+
+ def get_adaptor(self, format):
+ """
+ get specified format adaptor or
+ guess for a given filename
+ """
+ adaptor = self.adaptors.get(format, None)
+ if adaptor:
+ return adaptor
+
+ # not registered in adaptors dict, let's try all
+ for adaptor in self.adaptors.values():
+ if format in adaptor.extensions:
+ return adaptor
+
+ def filename2format(self, filename):
+ extension = os.path.splitext(filename)[-1]
+ return extension.lstrip('.') or None
+
+ def serialize(self, filename, format=None, full=False):
+ if not format:
+ format = self._format
+ if not format:
+ format = self.filename2format(filename)
+ if not format:
+ raise Exception('Please specify a format')
+ # TODO: more specific exception type
+
+ adaptor = self.get_adaptor(format)
+ if not adaptor:
+ raise Exception("Adaptor not found for format: %s" % format)
+
+ config = copy.deepcopy(self.config)
+ serializable = self.prep_value(config)
+ adaptor.write(serializable, filename)
+
+ if self.mtime:
+ self.touch_mtime(filename)
+
+ def touch_mtime(self, filename):
+ mtime = self.mtime
+ os.utime(filename, (mtime, mtime))
+
+ def deserialize(self, string=None, fromfile=None, format=None):
+ """
+ load configuration from a file or string
+ """
+
+ def _try_deserialize():
+ if fromfile:
+ with open(fromfile, 'r') as f:
+ content = adaptor.read(f)
+ elif string:
+ content = adaptor.read(string)
+ return content
+
+ # XXX cleanup this!
+
+ if fromfile:
+ leap_assert(os.path.exists(fromfile))
+ if not format:
+ format = self.filename2format(fromfile)
+
+ if not format:
+ format = self._format
+ if format:
+ adaptor = self.get_adaptor(format)
+ else:
+ adaptor = None
+
+ if adaptor:
+ content = _try_deserialize()
+ return content
+
+ # no adaptor, let's try rest of adaptors
+
+ adaptors = self.adaptors[:]
+
+ if format:
+ adaptors.sort(
+ key=lambda x: int(
+ format in x.extensions),
+ reverse=True)
+
+ for adaptor in adaptors:
+ content = _try_deserialize()
+ return content
+
+ def set_dirty(self):
+ self.dirty = True
+
+ def is_dirty(self):
+ return self.dirty
+
+ def load(self, *args, **kwargs):
+ """
+ load from string or file
+ if no string of fromfile option is given,
+ it will attempt to load from defaults
+ defined in the schema.
+ """
+ string = args[0] if args else None
+ fromfile = kwargs.get("fromfile", None)
+ mtime = kwargs.pop("mtime", None)
+ self.mtime = mtime
+ content = None
+
+ # start with defaults, so we can
+ # have partial values applied.
+ content = self.get_default_values()
+ if string and isinstance(string, str):
+ content = self.deserialize(string)
+
+ if not string and fromfile is not None:
+ #import ipdb;ipdb.set_trace()
+ content = self.deserialize(fromfile=fromfile)
+
+ if not content:
+ logger.error('no content could be loaded')
+ # XXX raise!
+ return
+
+ # lazy evaluation until first level of nesting
+ # to allow lambdas with context-dependant info
+ # like os.path.expanduser
+ for k, v in content.iteritems():
+ if callable(v):
+ content[k] = v()
+
+ self.validate(content)
+ self.config = content
+ return True
+
+
+def testmain(): # pragma: no cover
+
+ from tests import test_validation as t
+ import pprint
+
+ config = PluggableConfig(_format="json")
+ properties = copy.deepcopy(t.sample_spec)
+
+ config.options = properties
+ config.load(fromfile='data.json')
+
+ print 'config'
+ pprint.pprint(config.config)
+
+ config.serialize('/tmp/testserial.json')
+
+if __name__ == "__main__":
+ testmain()
diff --git a/src/leap/common/config/prefixers.py b/src/leap/common/config/prefixers.py
new file mode 100644
index 0000000..050d4cd
--- /dev/null
+++ b/src/leap/common/config/prefixers.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# prefixers.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/>.
+
+"""
+Platform dependant configuration path prefixers
+"""
+import os
+import platform
+
+from abc import ABCMeta, abstractmethod
+from xdg import BaseDirectory
+
+from leap.common.check import leap_assert
+
+
+class Prefixer:
+ """
+ Abstract prefixer class
+ """
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer
+
+ :param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ :type standalone: bool
+ """
+ return ""
+
+
+def get_platform_prefixer():
+ prefixer = globals()[platform.system() + "Prefixer"]
+ leap_assert(prefixer, "Unimplemented platform prefixer: %s" %
+ (platform.system(),))
+ return prefixer()
+
+
+class LinuxPrefixer(Prefixer):
+ """
+ Config prefixer for the Linux platform
+ """
+
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer.
+ This method expects an env variable named LEAP_CLIENT_PATH if
+ standalone is used.
+
+ :param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ :type standalone: bool
+ """
+ config_dir = BaseDirectory.xdg_config_home
+ if not standalone:
+ return config_dir
+ return os.path.join(os.getcwd(), "config")
+
+
+class DarwinPrefixer(Prefixer):
+ """
+ Config prefixer for the Darwin platform
+ """
+
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer.
+ This method expects an env variable named LEAP_CLIENT_PATH if
+ standalone is used.
+
+ :param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ :type standalone: bool
+ """
+ config_dir = BaseDirectory.xdg_config_home
+ if not standalone:
+ return config_dir
+ return os.getenv(os.getcwd(), "config")
+
+
+class WindowsPrefixer(Prefixer):
+ """
+ Config prefixer for the Windows platform
+ """
+
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer.
+ This method expects an env variable named LEAP_CLIENT_PATH if
+ standalone is used.
+
+ :param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ :type standalone: bool
+ """
+ config_dir = BaseDirectory.xdg_config_home
+
+ if not standalone:
+ return config_dir
+ return os.path.join(os.getcwd(), "config")
+
+if __name__ == "__main__":
+ try:
+ abs_prefixer = Prefixer()
+ except Exception as e:
+ assert isinstance(e, TypeError), "Something went wrong"
+ print "Abstract Prefixer class is working as expected"
+
+ linux_prefixer = LinuxPrefixer()
+ print linux_prefixer.get_path_prefix(standalone=True)
+ print linux_prefixer.get_path_prefix()
diff --git a/src/leap/common/events/Makefile b/src/leap/common/events/Makefile
index 4f99f35..4f73dea 100644
--- a/src/leap/common/events/Makefile
+++ b/src/leap/common/events/Makefile
@@ -21,10 +21,11 @@
PROTOC = protoc
-all: signal_pb2.py
+all: events_pb2.py
%_pb2.py: %.proto
$(PROTOC) --python_out=./ $<
+ autopep8 --in-place --aggressive $@
clean:
rm -f *_pb2.py
diff --git a/src/leap/common/events/README.rst b/src/leap/common/events/README.rst
new file mode 100644
index 0000000..813be8b
--- /dev/null
+++ b/src/leap/common/events/README.rst
@@ -0,0 +1,53 @@
+Events mechanism
+================
+
+The events mechanism allows for "components" to send signal events to each
+other by means of a centralized server. Components can register with the
+server to receive signals of certain types, and they can also send signals to
+the server that will then redistribute these signals to registered components.
+
+
+Listening daemons
+-----------------
+
+Both components and the server listen for incoming messages by using a
+listening daemon that runs in its own thread. The server daemon has to be
+started explicitly, while components daemon will be started whenever a
+component registers with the server to receive messages.
+
+
+How to use it
+-------------
+
+To start the events server:
+
+>>> from leap.common.events import server
+>>> server.ensure_server(port=8090)
+
+To register a callback to be called when a given signal is raised:
+
+>>> from leap.common.events import (
+>>> register,
+>>> events_pb2 as proto,
+>>> )
+>>>
+>>> def mycallback(sigreq):
+>>> print str(sigreq)
+>>>
+>>> events.register(signal=proto.CLIENT_UID, callback=mycallback)
+
+To signal an event:
+
+>>> from leap.common.events import (
+>>> signal,
+>>> events_pb2 as proto,
+>>> )
+>>> signal(proto.CLIENT_UID)
+
+Adding events
+-------------
+
+* Add the new event under enum ``Event`` in ``events.proto``
+* Compile the new protocolbuffers file::
+
+ make
diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py
index 27542a9..12416e4 100644
--- a/src/leap/common/events/__init__.py
+++ b/src/leap/common/events/__init__.py
@@ -15,37 +15,111 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+An events mechanism that allows for signaling of events between components.
+"""
+
+import logging
+import socket
+
+
from leap.common.events import (
- signal_pb2,
+ events_pb2 as proto,
+ server,
+ component,
+ daemon,
)
-# the `registered_callbacks` dictionary below should have the following
-# format:
-#
-# { component: [ (uid, callback), ... ], ... }
-#
-registered_callbacks = {}
+logger = logging.getLogger(__name__)
+
+
+def register(signal, callback, uid=None, replace=False, reqcbk=None,
+ timeout=1000):
+ """
+ Register a callback to be called when the given signal is received.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param callback: the callback to be called when the signal is received
+ :type callback: function
+ :param uid: a unique id for the callback
+ :type uid: int
+ :param replace: should an existent callback with same uid be replaced?
+ :type replace: bool
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ return component.register(signal, callback, uid, replace, reqcbk, timeout)
+
+
+def 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
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
-def register(signal, callback, uid=None, replace=False):
+ :return: the response from server for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
"""
- Registers `callback` to be called when `signal` is signaled.
+ return component.unregister(signal, uid, reqcbk, timeout)
+
+
+def signal(signal, content="", mac_method="", mac="", reqcbk=None,
+ timeout=1000):
+ """
+ Send `signal` event to events server.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param content: the contents of the event signal
+ :type content: str
+ :param mac_method: the method used to auth mac
+ :type mac_method: str
+ :param mac: the content of the auth mac
+ :type mac: str
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
"""
- if not registered_callbacks.has_key(signal):
- registered_callbacks[signal] = []
- cbklist = registered_callbacks[signal]
- if uid and filter(lambda (x,y): x == uid, cbklist):
- # TODO: create appropriate exception
- if not replace:
- raise Exception("Callback already registered.")
- else:
- registered_callbacks[signal] = filter(lambda(x,y): x != uid,
- cbklist)
- registered_callbacks[signal].append((uid, callback))
- return uid
-
-#def get_registered_callbacks():
-# return registered_callbacks
-
-#__all__ = ['signal_pb2', 'service', 'register', 'registered_callbacks']
+ return component.signal(signal, content, mac_method, mac, reqcbk, timeout)
diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py
new file mode 100644
index 0000000..029d1ac
--- /dev/null
+++ b/src/leap/common/events/component.py
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+# component.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+The component end point of the events mechanism.
+
+Components are the communicating parties of the events mechanism. They
+communicate by sending messages to a server, which in turn redistributes
+messages to other components.
+
+When a component registers a callback for a given signal, it also tells the
+server that it wants to be notified whenever signals of that type are sent by
+some other component.
+"""
+
+
+import logging
+import threading
+
+
+from protobuf.socketrpc import RpcService
+from leap.common.events import (
+ events_pb2 as proto,
+ server,
+ daemon,
+ mac_auth,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+# the `registered_callbacks` dictionary below should have the following
+# format:
+#
+# { event_signal: [ (uid, callback), ... ], ... }
+#
+registered_callbacks = {}
+
+
+class CallbackAlreadyRegistered(Exception):
+ """
+ Raised when trying to register an already registered callback.
+ """
+ pass
+
+
+def ensure_component_daemon():
+ """
+ Ensure the component daemon is running and listening for incoming
+ messages.
+
+ :return: the daemon instance
+ :rtype: EventsComponentDaemon
+ """
+ import time
+ daemon = EventsComponentDaemon.ensure(0)
+ logger.debug('ensure component daemon')
+
+ # Because we use a random port we want to wait until a port is assigned to
+ # local component daemon.
+
+ while not (EventsComponentDaemon.get_instance() and
+ EventsComponentDaemon.get_instance().get_port()):
+ time.sleep(0.1)
+ return daemon
+
+
+def register(signal, callback, uid=None, replace=False, reqcbk=None,
+ timeout=1000):
+ """
+ Registers a callback to be called when a specific signal event is
+ received.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param callback: the callback to be called when the signal is received
+ :type callback: function
+ callback(leap.common.events.events_pb2.SignalRequest)
+ :param uid: a unique id for the callback
+ :type uid: int
+ :param replace: should an existent callback with same uid be replaced?
+ :type replace: bool
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ Might raise a CallbackAlreadyRegistered exception if there's already a
+ callback identified by the given uid and replace is False.
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ ensure_component_daemon() # so we can receive registered signals
+ # register callback locally
+ if signal not in registered_callbacks:
+ registered_callbacks[signal] = []
+ cbklist = registered_callbacks[signal]
+ if uid and filter(lambda (x, y): x == uid, cbklist):
+ if not replace:
+ raise CallbackAlreadyRegisteredException()
+ else:
+ registered_callbacks[signal] = filter(lambda(x, y): x != uid,
+ cbklist)
+ registered_callbacks[signal].append((uid, callback))
+ # register callback on server
+ request = proto.RegisterRequest()
+ request.event = signal
+ request.port = EventsComponentDaemon.get_instance().get_port()
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(proto.EventsServerService_Stub,
+ server.SERVER_PORT, 'localhost')
+ logger.info(
+ "Sending registration request to server on port %s: %s",
+ server.SERVER_PORT,
+ 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
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls 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 = EventsComponentDaemon.get_instance().get_port()
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(proto.EventsServerService_Stub,
+ server.SERVER_PORT, 'localhost')
+ logger.info(
+ "Sending 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):
+ """
+ Send `signal` event to events server.
+
+ Will timeout after timeout ms if response has not been received. The
+ timeout arg is only used for asynch requests. If a reqcbk callback has
+ been supplied the timeout arg is not used. The response value will be
+ returned for a synch request but nothing will be returned for an asynch
+ request.
+
+ :param signal: the signal that causes the callback to be launched
+ :type signal: int (see the `events.proto` file)
+ :param content: the contents of the event signal
+ :type content: str
+ :param mac_method: the method used for auth mac
+ :type mac_method: str
+ :param mac: the content of the auth mac
+ :type mac: str
+ :param reqcbk: a callback to be called when a response from server is
+ received
+ :type reqcbk: function
+ callback(leap.common.events.events_pb2.EventResponse)
+ :param timeout: the timeout for synch calls
+ :type timeout: int
+
+ :return: the response from server for synch calls or nothing for asynch
+ calls.
+ :rtype: leap.common.events.events_pb2.EventsResponse or None
+ """
+ request = proto.SignalRequest()
+ request.event = signal
+ request.content = content
+ request.mac_method = mac_method
+ request.mac = mac
+ service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT,
+ 'localhost')
+ logger.info("Sending signal to server: %s", str(request)[:40])
+ return service.signal(request, callback=reqcbk, timeout=timeout)
+
+
+class EventsComponentService(proto.EventsComponentService):
+ """
+ Service for receiving signal events in components.
+ """
+
+ def __init__(self):
+ proto.EventsComponentService.__init__(self)
+
+ def signal(self, controller, request, done):
+ """
+ Receive a signal and run callbacks registered for that signal.
+
+ This method is called whenever a signal request is received from
+ server.
+
+ :param controller: used to mediate a single method call
+ :type controller: protobuf.socketrpc.controller.SocketRpcController
+ :param request: the request received from the component
+ :type request: leap.common.events.events_pb2.SignalRequest
+ :param done: callback to be called when done
+ :type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info('Received signal from server: %s...' % str(request)[:40])
+
+ # run registered callbacks
+ # TODO: verify authentication using mac in incoming message
+ if request.event in registered_callbacks:
+ for (_, cbk) in registered_callbacks[request.event]:
+ # callbacks should be prepared to receive a
+ # events_pb2.SignalRequest.
+ cbk(request)
+
+ # send response back to server
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+
+class EventsComponentDaemon(daemon.EventsSingletonDaemon):
+ """
+ A daemon that listens for incoming events from server.
+ """
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon is running on the given port.
+
+ :param port: the port in which the daemon should listen
+ :type port: int
+
+ :return: a daemon instance
+ :rtype: EventsComponentDaemon
+ """
+ return cls.ensure_service(port, EventsComponentService())
diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py
new file mode 100644
index 0000000..c253948
--- /dev/null
+++ b/src/leap/common/events/daemon.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# daemon.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+A singleton daemon for running RPC services using protobuf.socketrpc.
+"""
+
+
+import logging
+import threading
+
+
+from protobuf.socketrpc.server import (
+ SocketRpcServer,
+ ThreadedTCPServer,
+ SocketHandler,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+class ServiceAlreadyRunningException(Exception):
+ """
+ Raised whenever a service is already running in this process but someone
+ attemped to start it in a different port.
+ """
+
+
+class EventsRpcServer(SocketRpcServer):
+ """
+ RPC server used in server and component interfaces to receive messages.
+ """
+
+ def __init__(self, port, host='localhost'):
+ """
+ Initialize a RPC server.
+
+ :param port: the port in which to listen for incoming messages
+ :type port: int
+ :param host: the address to bind to
+ :type host: str
+ """
+ SocketRpcServer.__init__(self, port, host)
+ self._server = None
+
+ def run(self):
+ """
+ Run the server.
+ """
+ logger.info('Running server on port %d.' % self.port)
+ # parent implementation does not hold the server instance, so we do it
+ # here.
+ self._server = ThreadedTCPServer((self.host, self.port),
+ SocketHandler, self)
+ # if we chose to use a random port, fetch the port number info.
+ if self.port is 0:
+ self.port = self._server.socket.getsockname()[1]
+ self._server.serve_forever()
+
+ def stop(self):
+ """
+ Stop the server.
+ """
+ self._server.shutdown()
+
+
+class EventsSingletonDaemon(threading.Thread):
+ """
+ Singleton class for for launching and terminating a daemon.
+
+ This class is used so every part of the mechanism that needs to listen for
+ messages can launch its own daemon (thread) to do the job.
+ """
+
+ # Singleton instance
+ __instance = None
+
+ def __new__(cls, *args, **kwargs):
+ """
+ Return a singleton instance if it exists or create and initialize one.
+ """
+ if len(args) is not 2:
+ raise TypeError("__init__() takes exactly 2 arguments (%d given)"
+ % len(args))
+ if cls.__instance is None:
+ cls.__instance = object.__new__(
+ EventsSingletonDaemon)
+ cls.__initialize(cls.__instance, args[0], args[1])
+ return cls.__instance
+
+ @staticmethod
+ def __initialize(self, port, service):
+ """
+ Initialize a singleton daemon.
+
+ This is a static method disguised as instance method that actually
+ does the initialization of the daemon instance.
+
+ :param port: the port in which to listen for incoming messages
+ :type port: int
+ :param service: the service to provide in this daemon
+ :type service: google.protobuf.service.Service
+ """
+ threading.Thread.__init__(self)
+ self._port = port
+ self._service = service
+ self._server = EventsRpcServer(self._port)
+ self._server.registerService(self._service)
+ self.daemon = True
+
+ def __init__(self):
+ """
+ Singleton placeholder initialization method.
+
+ Initialization is made in __new__ so we can always return the same
+ instance upon object creation.
+ """
+ pass
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon instance is running.
+
+ Each implementation of this method should call `self.ensure_service`
+ with the appropriate service from the `events.proto` definitions, and
+ return the daemon instance.
+
+ :param port: the port in which the daemon should be listening
+ :type port: int
+
+ :return: a daemon instance
+ :rtype: EventsSingletonDaemon
+ """
+ raise NotImplementedError(self.ensure)
+
+ @classmethod
+ def ensure_service(cls, port, service):
+ """
+ Start the singleton instance if not already running.
+
+ Might return ServiceAlreadyRunningException
+
+ :param port: the port in which the daemon should be listening
+ :type port: int
+
+ :return: a daemon instance
+ :rtype: EventsSingletonDaemon
+ """
+ daemon = cls(port, service)
+ if not daemon.is_alive():
+ daemon.start()
+ elif port and port != cls.__instance._port:
+ # service is running in this process but someone is trying to
+ # start it in another port
+ raise ServiceAlreadyRunningException(
+ "Service is already running in this process on port %d."
+ % self.__instance._port)
+ return daemon
+
+ @classmethod
+ def get_instance(cls):
+ """
+ Retrieve singleton instance of this daemon.
+
+ :return: a daemon instance
+ :rtype: EventsSingletonDaemon
+ """
+ return cls.__instance
+
+ def run(self):
+ """
+ Run the server.
+ """
+ self._server.run()
+
+ def stop(self):
+ """
+ Stop the daemon.
+ """
+ self._server.stop()
+
+ def get_port(self):
+ """
+ Retrieve the value of the port to which the service running in this
+ daemon is binded to.
+
+ :return: the port to which the daemon is binded to
+ :rtype: int
+ """
+ if self._port is 0:
+ self._port = self._server.port
+ return self._port
diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto
new file mode 100644
index 0000000..a813ed1
--- /dev/null
+++ b/src/leap/common/events/events.proto
@@ -0,0 +1,79 @@
+// signal.proto
+// 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/>.
+
+package leap.common.events;
+option py_generic_services = true;
+
+enum Event {
+ CLIENT_SESSION_ID = 1;
+ CLIENT_UID = 2;
+ SOLEDAD_CREATING_KEYS = 3;
+ SOLEDAD_DONE_CREATING_KEYS = 4;
+ SOLEDAD_UPLOADING_KEYS = 5;
+ SOLEDAD_DONE_UPLOADING_KEYS = 6;
+ SOLEDAD_DOWNLOADING_KEYS = 7;
+ SOLEDAD_DONE_DOWNLOADING_KEYS = 8;
+ SOLEDAD_NEW_DATA_TO_SYNC = 9;
+ SOLEDAD_DONE_DATA_SYNC = 10;
+ UPDATER_NEW_UPDATES = 11;
+ UPDATER_DONE_UPDATING = 12;
+ RAISE_WINDOW = 13;
+}
+
+message SignalRequest {
+ required Event event = 1;
+ required string content = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+ optional string enc_method = 5;
+ optional bool error_occurred = 6;
+}
+
+message RegisterRequest {
+ required Event event = 1;
+ required int32 port = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+}
+
+message UnregisterRequest {
+ required Event event = 1;
+ required int32 port = 2;
+ required string mac_method = 3;
+ required bytes mac = 4;
+}
+
+message EventResponse {
+
+ enum Status {
+ OK = 1;
+ UNAUTH = 2;
+ ERROR = 3;
+ }
+
+ required Status status = 1;
+ optional string result = 2;
+}
+
+service EventsServerService {
+ rpc register(RegisterRequest) returns (EventResponse);
+ rpc unregister(UnregisterRequest) returns (EventResponse);
+ rpc signal(SignalRequest) returns (EventResponse);
+}
+
+service EventsComponentService {
+ rpc signal(SignalRequest) returns (EventResponse);
+}
diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py
new file mode 100644
index 0000000..5b1c118
--- /dev/null
+++ b/src/leap/common/events/events_pb2.py
@@ -0,0 +1,444 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: events.proto
+
+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(
+ 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\"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\"\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\x91\x02\n\x13\x45ventsServerService\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.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01')
+
+_EVENT = _descriptor.EnumDescriptor(
+ name='Event',
+ full_name='leap.common.events.Event',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='CLIENT_SESSION_ID', index=0, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='CLIENT_UID', index=1, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_CREATING_KEYS', index=2, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_UPLOADING_KEYS', index=4, number=5,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UPDATER_NEW_UPDATES', index=10, number=11,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UPDATER_DONE_UPDATING', index=11, number=12,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='RAISE_WINDOW', index=12, number=13,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=542,
+ serialized_end=901,
+)
+
+Event = enum_type_wrapper.EnumTypeWrapper(_EVENT)
+CLIENT_SESSION_ID = 1
+CLIENT_UID = 2
+SOLEDAD_CREATING_KEYS = 3
+SOLEDAD_DONE_CREATING_KEYS = 4
+SOLEDAD_UPLOADING_KEYS = 5
+SOLEDAD_DONE_UPLOADING_KEYS = 6
+SOLEDAD_DOWNLOADING_KEYS = 7
+SOLEDAD_DONE_DOWNLOADING_KEYS = 8
+SOLEDAD_NEW_DATA_TO_SYNC = 9
+SOLEDAD_DONE_DATA_SYNC = 10
+UPDATER_NEW_UPDATES = 11
+UPDATER_DONE_UPDATING = 12
+RAISE_WINDOW = 13
+
+
+_EVENTRESPONSE_STATUS = _descriptor.EnumDescriptor(
+ name='Status',
+ full_name='leap.common.events.EventResponse.Status',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='OK', index=0, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UNAUTH', index=1, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR', index=2, number=3,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=500,
+ serialized_end=539,
+)
+
+
+_SIGNALREQUEST = _descriptor.Descriptor(
+ name='SignalRequest',
+ full_name='leap.common.events.SignalRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='event', full_name='leap.common.events.SignalRequest.event', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='content', full_name='leap.common.events.SignalRequest.content', index=1,
+ number=2, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2,
+ number=3, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='mac', full_name='leap.common.events.SignalRequest.mac', index=3,
+ number=4, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value="",
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5,
+ number=6, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=37,
+ serialized_end=188,
+)
+
+
+_REGISTERREQUEST = _descriptor.Descriptor(
+ name='RegisterRequest',
+ full_name='leap.common.events.RegisterRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='event', full_name='leap.common.events.RegisterRequest.event', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='port', full_name='leap.common.events.RegisterRequest.port', index=1,
+ number=2, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2,
+ number=3, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3,
+ number=4, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value="",
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=190,
+ serialized_end=296,
+)
+
+
+_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,
+)
+
+
+_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=409,
+ serialized_end=539,
+)
+
+_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
+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['EventResponse'] = _EVENTRESPONSE
+
+
+class SignalRequest(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _SIGNALREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest)
+
+
+class RegisterRequest(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _REGISTERREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest)
+
+
+class UnregisterRequest(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _UNREGISTERREQUEST
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.UnregisterRequest)
+
+
+class EventResponse(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _EVENTRESPONSE
+
+ # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse)
+
+
+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,
+ options=None,
+ serialized_start=904,
+ serialized_end=1177,
+ methods=[
+ _descriptor.MethodDescriptor(
+ name='register',
+ full_name='leap.common.events.EventsServerService.register',
+ index=0,
+ containing_service=None,
+ input_type=_REGISTERREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='unregister',
+ full_name='leap.common.events.EventsServerService.unregister',
+ index=1,
+ containing_service=None,
+ input_type=_UNREGISTERREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ _descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsServerService.signal',
+ index=2,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
+
+
+class EventsServerService(_service.Service):
+ __metaclass__ = service_reflection.GeneratedServiceType
+ DESCRIPTOR = _EVENTSSERVERSERVICE
+
+
+class EventsServerService_Stub(EventsServerService):
+ __metaclass__ = service_reflection.GeneratedServiceStubType
+ DESCRIPTOR = _EVENTSSERVERSERVICE
+
+
+_EVENTSCOMPONENTSERVICE = _descriptor.ServiceDescriptor(
+ name='EventsComponentService',
+ full_name='leap.common.events.EventsComponentService',
+ file=DESCRIPTOR,
+ index=1,
+ options=None,
+ serialized_start=1179,
+ serialized_end=1283,
+ methods=[
+ _descriptor.MethodDescriptor(
+ name='signal',
+ full_name='leap.common.events.EventsComponentService.signal',
+ index=0,
+ containing_service=None,
+ input_type=_SIGNALREQUEST,
+ output_type=_EVENTRESPONSE,
+ options=None,
+ ),
+ ])
+
+
+class EventsComponentService(_service.Service):
+ __metaclass__ = service_reflection.GeneratedServiceType
+ DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+
+
+class EventsComponentService_Stub(EventsComponentService):
+ __metaclass__ = service_reflection.GeneratedServiceStubType
+ DESCRIPTOR = _EVENTSCOMPONENTSERVICE
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/leap/common/events/mac_auth.py b/src/leap/common/events/mac_auth.py
new file mode 100644
index 0000000..49d48f7
--- /dev/null
+++ b/src/leap/common/events/mac_auth.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# mac_auth.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Authentication system for events.
+
+This is not implemented yet.
+"""
+
+
+class MacMethod(object):
+ """
+ Representation of possible MAC authentication methods.
+ """
+
+ MAC_NONE = 'none'
+ MAC_HMAC = 'hmac'
diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py
new file mode 100644
index 0000000..d53c218
--- /dev/null
+++ b/src/leap/common/events/server.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+# server.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+A server for the events mechanism.
+
+A server can receive different kinds of requests from components:
+
+ 1. Registration request: store component port number to be notified when
+ a specific signal arrives.
+
+ 2. Signal request: redistribute the signal to registered components.
+"""
+import logging
+import socket
+
+
+from protobuf.socketrpc import RpcService
+from leap.common.events import (
+ events_pb2 as proto,
+ daemon,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+SERVER_PORT = 8090
+
+# the `registered_components` dictionary below should have the following
+# format:
+#
+# { event_signal: [ port, ... ], ... }
+#
+registered_components = {}
+
+
+def ensure_server(port=SERVER_PORT):
+ """
+ Make sure the server is running on the given port.
+
+ Attempt to connect to given local port. Upon success, assume that the
+ events server has already been started. Upon failure, start events server.
+
+ :param port: the port in which server should be listening
+ :type port: int
+
+ :return: the daemon instance or nothing
+ :rtype: EventsServerDaemon or None
+ """
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', port))
+ s.close()
+ logger.info('Server is already running on port %d.', port)
+ return None
+ except socket.error:
+ logger.info('Launching server on port %d.', port)
+ return EventsServerDaemon.ensure(port)
+
+
+class EventsServerService(proto.EventsServerService):
+ """
+ Service for receiving events in components.
+ """
+
+ def register(self, controller, request, done):
+ """
+ Register a component port to be signaled when specific events come in.
+
+ :param controller: used to mediate a single method call
+ :type controller: protobuf.socketrpc.controller.SocketRpcController
+ :param request: the request received from the component
+ :type request: leap.common.events.events_pb2.RegisterRequest
+ :param done: callback to be called when done
+ :type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info("Received registration request: %s..." % str(request)[: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
+
+ logger.debug('sending response back')
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+ def unregister(self, controller, request, done):
+ """
+ Unregister a component 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 component
+ :type request: leap.common.events.events_pb2.RegisterRequest
+ :param done: callback to be called when done
+ :type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info(
+ "Received unregistration request: %s..." % str(request)[:40])
+ # remove component port from signal list
+ response = proto.EventResponse()
+ if request.event in registered_components:
+ try:
+ registered_components[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 component
+ 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
+ specific signal.
+
+ :param controller: used to mediate a single method call
+ :type controller: protobuf.socketrpc.controller.SocketRpcController
+ :param request: the request received from the component
+ :type request: leap.common.events.events_pb2.SignalRequest
+ :param done: callback to be called when done
+ :type done: protobuf.socketrpc.server.Callback
+ """
+ logger.info('Received signal from component: %s...', str(request)[:40])
+ # send signal to all registered components
+ # TODO: verify signal auth
+ if request.event in registered_components:
+ for port in registered_components[request.event]:
+
+ def callback(req, resp):
+ logger.info("Signal received by " + str(port))
+
+ service = RpcService(proto.EventsComponentService_Stub,
+ port, 'localhost')
+ service.signal(request, callback=callback)
+ # send response back to component
+ response = proto.EventResponse()
+ response.status = proto.EventResponse.OK
+ done.run(response)
+
+
+class EventsServerDaemon(daemon.EventsSingletonDaemon):
+ """
+ Singleton class for starting an events server daemon.
+ """
+
+ @classmethod
+ def ensure(cls, port):
+ """
+ Make sure the daemon is running on the given port.
+
+ :param port: the port in which the daemon should listen
+ :type port: int
+
+ :return: a daemon instance
+ :rtype: EventsServerDaemon
+ """
+ return cls.ensure_service(port, EventsServerService())
diff --git a/src/leap/common/events/service.py b/src/leap/common/events/service.py
deleted file mode 100644
index fda45b2..0000000
--- a/src/leap/common/events/service.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- coding: utf-8 -*-
-# service.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 logging
-import threading
-from protobuf.socketrpc.server import (
- SocketRpcServer,
- ThreadedTCPServer,
- SocketHandler,
-)
-from leap.common.events import (
- signal_pb2 as proto,
- registered_callbacks,
-)
-
-
-logger = logging.getLogger(__name__)
-
-
-class SignalRpcServer(SocketRpcServer):
-
- def __init__(self, port, host='localhost'):
- '''port - Port this server is started on'''
- self.port = port
- self.host = host
- self.serviceMap = {}
- self.server = None
-
- def run(self):
- '''Activate the server.'''
- logger.info('Running server on port %d' % self.port)
- self.server = ThreadedTCPServer((self.host, self.port),
- SocketHandler, self)
- self.server.serve_forever()
-
- def stop(self):
- self.server.shutdown()
-
-
-class SignalService(proto.SignalService):
- '''
- Handles signaling for LEAP components.
- '''
-
- def signal(self, controller, request, done):
- logger.info('Received signal.')
-
- # Run registered callbacks
- if registered_callbacks.has_key(request.signal):
- for (_, cbk) in registered_callbacks[request.signal]:
- cbk(request)
-
- # Create response message
- response = proto.SignalResponse()
- # TODO: change id for something meaningful
- response.id = 1
- response.status = proto.SignalResponse.OK
-
- # Call provided callback with response message
- done.run(response)
-
-
-class SignalServiceThread(threading.Thread):
- """
- Singleton class for starting a server thread
- """
-
- # Singleton instance
- _instance = None
-
- def __init__(self, port):
- super(SignalServiceThread, self).__init__()
- self._service = SignalService()
- self._port = port
- self._server = SignalRpcServer(self._port)
- self._server.registerService(self._service)
- self.setDaemon(True)
-
- @staticmethod
- def start_service(port):
- """
- Start the singleton instance if not already running
- Will not exit until the process ends
- """
- if SignalServiceThread._instance == None:
- SignalServiceThread._instance = SignalServiceThread(port)
- SignalServiceThread._instance.start()
- elif port != SignalServiceThread._instance._port:
- # TODO: make this exception more self-explanatory
- raise Exception()
- return SignalServiceThread._instance
-
- def get_instance(self):
- return self._instance
-
- def run(self):
- self._server.run()
-
- def stop(self):
- self._server.stop()
diff --git a/src/leap/common/events/signal.proto b/src/leap/common/events/signal.proto
deleted file mode 100644
index 336471c..0000000
--- a/src/leap/common/events/signal.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-// signal.proto
-// Copyright (C) 2013 LEA
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-package leap.common.events;
-
-message SignalRequest {
-
- enum Signal {
- CLIENT_SESSION_ID = 1;
- CLIENT_UID = 2;
- SOLEDAD_CREATING_KEYS = 3;
- SOLEDAD_DONE_CREATING_KEYS = 4;
- SOLEDAD_UPLOADING_KEYS = 5;
- SOLEDAD_DONE_UPLOADING_KEYS = 6;
- SOLEDAD_DOWNLOADING_KEYS = 7;
- SOLEDAD_DONE_DOWNLOADING_KEYS = 8;
- SOLEDAD_NEW_DATA_TO_SYNC = 9;
- SOLEDAD_DONE_DATA_SYNC = 10;
- }
-
- required int32 id = 1;
- required Signal signal = 2;
- required string content = 3;
- required string mac_method = 4;
- required bytes mac = 5;
- optional string enc_method = 6;
- optional bool error_occurred = 7;
-}
-
-message SignalResponse {
-
- enum Status {
- OK = 1;
- UNAUTH = 2;
- ERROR = 3;
- }
-
- required int32 id = 1;
- required Status status = 2;
-}
-
-service SignalService {
- rpc signal(SignalRequest) returns (SignalResponse);
-}
diff --git a/src/leap/common/events/signal_pb2.py b/src/leap/common/events/signal_pb2.py
deleted file mode 100644
index b21676f..0000000
--- a/src/leap/common/events/signal_pb2.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-
-from google.protobuf import descriptor
-from google.protobuf import message
-from google.protobuf import reflection
-from google.protobuf import service
-from google.protobuf import service_reflection
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-
-DESCRIPTOR = descriptor.FileDescriptor(
- name='signal.proto',
- package='leap.common.events',
- serialized_pb='\n\x0csignal.proto\x12\x12leap.common.events\"\xd8\x03\n\rSignalRequest\x12\n\n\x02id\x18\x01 \x02(\x05\x12\x38\n\x06signal\x18\x02 \x02(\x0e\x32(.leap.common.events.SignalRequest.Signal\x12\x0f\n\x07\x63ontent\x18\x03 \x02(\t\x12\x12\n\nmac_method\x18\x04 \x02(\t\x12\x0b\n\x03mac\x18\x05 \x02(\x0c\x12\x12\n\nenc_method\x18\x06 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x07 \x01(\x08\"\xa2\x02\n\x06Signal\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\"\x80\x01\n\x0eSignalResponse\x12\n\n\x02id\x18\x01 \x02(\x05\x12\x39\n\x06status\x18\x02 \x02(\x0e\x32).leap.common.events.SignalResponse.Status\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03\x32`\n\rSignalService\x12O\n\x06signal\x12!.leap.common.events.SignalRequest\x1a\".leap.common.events.SignalResponse')
-
-
-
-_SIGNALREQUEST_SIGNAL = descriptor.EnumDescriptor(
- name='Signal',
- full_name='leap.common.events.SignalRequest.Signal',
- filename=None,
- file=DESCRIPTOR,
- values=[
- descriptor.EnumValueDescriptor(
- name='CLIENT_SESSION_ID', index=0, number=1,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='CLIENT_UID', index=1, number=2,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_CREATING_KEYS', index=2, number=3,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_UPLOADING_KEYS', index=4, number=5,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=219,
- serialized_end=509,
-)
-
-_SIGNALRESPONSE_STATUS = descriptor.EnumDescriptor(
- name='Status',
- full_name='leap.common.events.SignalResponse.Status',
- filename=None,
- file=DESCRIPTOR,
- values=[
- descriptor.EnumValueDescriptor(
- name='OK', index=0, number=1,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='UNAUTH', index=1, number=2,
- options=None,
- type=None),
- descriptor.EnumValueDescriptor(
- name='ERROR', index=2, number=3,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=601,
- serialized_end=640,
-)
-
-
-_SIGNALREQUEST = descriptor.Descriptor(
- name='SignalRequest',
- full_name='leap.common.events.SignalRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- descriptor.FieldDescriptor(
- name='id', full_name='leap.common.events.SignalRequest.id', index=0,
- number=1, 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='signal', full_name='leap.common.events.SignalRequest.signal', index=1,
- number=2, 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=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_method', full_name='leap.common.events.SignalRequest.mac_method', index=3,
- number=4, 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=4,
- number=5, 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=5,
- number=6, 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=6,
- number=7, 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=[
- _SIGNALREQUEST_SIGNAL,
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=37,
- serialized_end=509,
-)
-
-
-_SIGNALRESPONSE = descriptor.Descriptor(
- name='SignalResponse',
- full_name='leap.common.events.SignalResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- descriptor.FieldDescriptor(
- name='id', full_name='leap.common.events.SignalResponse.id', index=0,
- number=1, 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='status', full_name='leap.common.events.SignalResponse.status', index=1,
- number=2, 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),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _SIGNALRESPONSE_STATUS,
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=512,
- serialized_end=640,
-)
-
-
-_SIGNALREQUEST.fields_by_name['signal'].enum_type = _SIGNALREQUEST_SIGNAL
-_SIGNALREQUEST_SIGNAL.containing_type = _SIGNALREQUEST;
-_SIGNALRESPONSE.fields_by_name['status'].enum_type = _SIGNALRESPONSE_STATUS
-_SIGNALRESPONSE_STATUS.containing_type = _SIGNALRESPONSE;
-
-class SignalRequest(message.Message):
- __metaclass__ = reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _SIGNALREQUEST
-
- # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest)
-
-class SignalResponse(message.Message):
- __metaclass__ = reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _SIGNALRESPONSE
-
- # @@protoc_insertion_point(class_scope:leap.common.events.SignalResponse)
-
-
-_SIGNALSERVICE = descriptor.ServiceDescriptor(
- name='SignalService',
- full_name='leap.common.events.SignalService',
- file=DESCRIPTOR,
- index=0,
- options=None,
- serialized_start=642,
- serialized_end=738,
- methods=[
- descriptor.MethodDescriptor(
- name='signal',
- full_name='leap.common.events.SignalService.signal',
- index=0,
- containing_service=None,
- input_type=_SIGNALREQUEST,
- output_type=_SIGNALRESPONSE,
- options=None,
- ),
-])
-
-class SignalService(service.Service):
- __metaclass__ = service_reflection.GeneratedServiceType
- DESCRIPTOR = _SIGNALSERVICE
-class SignalService_Stub(SignalService):
- __metaclass__ = service_reflection.GeneratedServiceStubType
- DESCRIPTOR = _SIGNALSERVICE
-
-# @@protoc_insertion_point(module_scope)
diff --git a/src/leap/common/events/test_events.py b/src/leap/common/events/test_events.py
deleted file mode 100644
index ae55319..0000000
--- a/src/leap/common/events/test_events.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import unittest
-from protobuf.socketrpc import RpcService
-from leap.common import events
-from leap.common.events import service
-from leap.common.events.signal_pb2 import (
- SignalRequest,
- SignalService,
- SignalService_Stub,
-)
-
-
-port = 8090
-
-class EventsTestCase(unittest.TestCase):
-
- def _start_service(self):
- return service.SignalServiceThread.start_service(port)
-
- def setUp(self):
- super(EventsTestCase, self).setUp()
- self._service = self._start_service()
-
- def tearDown(self):
- events.registered_callbacks = {}
- super(EventsTestCase, self).tearDown()
-
- def test_service_singleton(self):
- self.assertTrue(self._service.get_instance() == self._service,
- "Can't get singleton class for service.")
-
- def test_register_signal(self):
- key = SignalRequest.SOLEDAD_CREATING_KEYS
- self.assertEqual({}, events.registered_callbacks,
- 'There should be no registered_callbacks events when '
- 'service has just started.')
- events.register(key, lambda x: True)
- self.assertEqual(1, len(events.registered_callbacks),
- 'Wrong number of registered callbacks.')
- self.assertEqual(events.registered_callbacks.keys(), [key],
- 'Couldn\'t locate registered signal.')
- events.register(key, lambda x: True)
- self.assertEqual(1, len(events.registered_callbacks),
- 'Wrong number of registered callbacks.')
- self.assertEqual(events.registered_callbacks.keys(), [key],
- 'Couldn\'t locate registered signal.')
- self.assertEqual(
- 2,
- len(events.registered_callbacks[SignalRequest.SOLEDAD_CREATING_KEYS]),
- 'Wrong number of registered callbacks.')
- key2 = SignalRequest.CLIENT_UID
- events.register(key2, lambda x: True)
- self.assertEqual(2, len(events.registered_callbacks),
- 'Wrong number of registered callbacks.')
- self.assertEqual(
- sorted(events.registered_callbacks.keys()),
- sorted([key2, key]),
- 'Wrong keys in `registered_keys`.')
-
- def test_register_signal_replace(self):
- key = SignalRequest.SOLEDAD_CREATING_KEYS
- cbk = lambda x: True
- self.assertEqual({}, events.registered_callbacks,
- 'There should be no registered_callbacks events when '
- 'service has just started.')
- events.register(key, cbk, uid=1)
- self.assertRaises(Exception, events.register, key, lambda x: True, uid=1)
- self.assertEquals(1,
- events.register(key, lambda x: True, uid=1, replace=True),
- "Could not replace callback.")
- self.assertEqual(1, len(events.registered_callbacks),
- 'Wrong number of registered callbacks.')
- self.assertEqual(events.registered_callbacks.keys(), [key],
- 'Couldn\'t locate registered signal.')
-
- def test_signal_response_status(self):
- sig = SignalRequest.SOLEDAD_CREATING_KEYS
- cbk = lambda x: True
- events.register(sig, cbk)
- request = SignalRequest()
- request.id = 1
- request.signal = sig
- request.content = 'my signal contents'
- request.mac_method = 'nomac'
- request.mac = ""
- service = RpcService(SignalService_Stub, port, 'localhost')
- response = service.signal(request, timeout=1000)
- self.assertEqual(response.OK, response.status,
- 'Wrong response status.')
diff --git a/src/leap/common/files.py b/src/leap/common/files.py
index 4c443dd..bba281b 100644
--- a/src/leap/common/files.py
+++ b/src/leap/common/files.py
@@ -33,8 +33,8 @@ def check_and_fix_urw_only(cert):
Might raise OSError
- @param cert: Certificate path
- @type cert: str
+ :param cert: Certificate path
+ :type cert: str
"""
mode = stat.S_IMODE(os.stat(cert).st_mode)
@@ -53,10 +53,10 @@ def get_mtime(filename):
"""
Returns the modified time or None if the file doesn't exist
- @param filename: path to check
- @type filename: str
+ :param filename: path to check
+ :type filename: str
- @rtype: str
+ :rtype: str
"""
try:
mtime = time.ctime(os.path.getmtime(filename)) + " GMT"
@@ -72,8 +72,8 @@ def mkdir_p(path):
Might raise OSError
- @param path: path to create
- @type path: str
+ :param path: path to create
+ :type path: str
"""
try:
os.makedirs(path)
@@ -97,14 +97,14 @@ def which(name, flags=os.X_OK, path_extension="/usr/sbin:/sbin"):
On MS-Windows the only flag that has any meaning is os.F_OK. Any other
flags will be ignored.
- @type name: C{str}
- @param name: The name for which to search.
+ :type name: C{str}
+ :param name: The name for which to search.
- @type flags: C{int}
- @param flags: Arguments to L{os.access}.
+ :type flags: C{int}
+ :param flags: Arguments to L{os.access}.
- @rtype: C{list}
- @param: A list of the full paths to files found, in the
+ :rtype: C{list}
+ :param: A list of the full paths to files found, in the
order in which they were found.
"""
diff --git a/src/leap/common/testing/basetest.py b/src/leap/common/testing/basetest.py
index 65e23a9..8890bf9 100644
--- a/src/leap/common/testing/basetest.py
+++ b/src/leap/common/testing/basetest.py
@@ -94,8 +94,8 @@ class BaseLeapTest(unittest.TestCase):
Raises NotImplementedError for this platform
if do_raise is True
- @param do_raise: flag to actually raise exception
- @type do_raise: bool
+ :param do_raise: flag to actually raise exception
+ :type do_raise: bool
"""
if do_raise:
raise NotImplementedError(
@@ -109,8 +109,8 @@ class BaseLeapTest(unittest.TestCase):
prepending the temporal dir associated with this
TestCase
- @param filename: the filename
- @type filename: str
+ :param filename: the filename
+ :type filename: str
"""
return os.path.join(self.tempdir, filename)
@@ -119,8 +119,8 @@ class BaseLeapTest(unittest.TestCase):
Touches a filepath, creating folders along
the way if needed.
- @param filepath: path to be touched
- @type filepath: str
+ :param filepath: path to be touched
+ :type filepath: str
"""
folder, filename = os.path.split(filepath)
if not os.path.isdir(folder):
@@ -134,7 +134,7 @@ class BaseLeapTest(unittest.TestCase):
"""
Chmods 600 a file
- @param filepath: filepath to be chmodded
- @type filepath: str
+ :param filepath: filepath to be chmodded
+ :type filepath: str
"""
check_and_fix_urw_only(filepath)
diff --git a/src/leap/common/testing/test_basetest.py b/src/leap/common/testing/test_basetest.py
index c4636df..cf0962d 100644
--- a/src/leap/common/testing/test_basetest.py
+++ b/src/leap/common/testing/test_basetest.py
@@ -25,7 +25,7 @@ except ImportError:
import os
import StringIO
-from leap.testing.basetest import BaseLeapTest
+from leap.common.testing.basetest import BaseLeapTest
_tempdir = None # global for tempdir checking
@@ -38,8 +38,8 @@ class _TestCaseRunner(object):
"""
Runs a given TestCase
- @param testcase: the testcase
- @type testcase: unittest.TestCase
+ :param testcase: the testcase
+ :type testcase: unittest.TestCase
"""
if not testcase:
return None
diff --git a/src/leap/common/tests/__init__.py b/src/leap/common/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/leap/common/tests/__init__.py
diff --git a/src/leap/common/tests/test_events.py b/src/leap/common/tests/test_events.py
new file mode 100644
index 0000000..7286bdc
--- /dev/null
+++ b/src/leap/common/tests/test_events.py
@@ -0,0 +1,233 @@
+## -*- coding: utf-8 -*-
+# test_events.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 unittest
+import sets
+import time
+from protobuf.socketrpc import RpcService
+from leap.common import events
+from leap.common.events import (
+ server,
+ component,
+ mac_auth,
+)
+from leap.common.events.events_pb2 import (
+ EventsServerService,
+ EventsServerService_Stub,
+ EventResponse,
+ SignalRequest,
+ RegisterRequest,
+ SOLEDAD_CREATING_KEYS,
+ CLIENT_UID,
+)
+
+
+port = 8090
+
+received = False
+local_callback_executed = False
+
+
+def callback(request, reponse):
+ return True
+
+
+class EventsTestCase(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ server.EventsServerDaemon.ensure(8090)
+ cls.callbacks = events.component.registered_callbacks
+
+ @classmethod
+ def tearDownClass(cls):
+ # give some time for requests to be processed.
+ time.sleep(1)
+
+ def setUp(self):
+ super(EventsTestCase, self).setUp()
+
+ def tearDown(self):
+ #events.component.registered_callbacks = {}
+ server.registered_components = {}
+ super(EventsTestCase, self).tearDown()
+
+ def test_service_singleton(self):
+ """
+ Ensure that there's always just one instance of the server daemon
+ running.
+ """
+ service1 = server.EventsServerDaemon.ensure(8090)
+ service2 = server.EventsServerDaemon.ensure(8090)
+ self.assertEqual(service1, service2,
+ "Can't get singleton class for service.")
+
+ def test_component_register(self):
+ """
+ Ensure components 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.')
+ events.register(2, lambda x: True)
+ self.assertTrue(1 in self.callbacks,
+ 'Could not register signal in local component.')
+ self.assertTrue(2 in self.callbacks,
+ 'Could not register signal in local component.')
+
+ def test_register_signal_replace(self):
+ """
+ Make sure components can replace already registered callbacks.
+ """
+ sig = 3
+ cbk = lambda x: True
+ events.register(sig, cbk, uid=1)
+ self.assertRaises(Exception, events.register, sig, lambda x: True,
+ uid=1)
+ events.register(sig, lambda x: True, uid=1, replace=True)
+ self.assertTrue(sig in self.callbacks, 'Could not register signal.')
+ self.assertEqual(1, len(self.callbacks[sig]),
+ 'Wrong number of registered callbacks.')
+
+ def test_signal_response_status(self):
+ """
+ Ensure there's an appropriate response from server when signaling.
+ """
+ sig = 4
+ 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')
+ # test synch
+ 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
+
+ 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.')
+
+ def test_events_server_service_register(self):
+ """
+ Ensure the server can register components to be signaled.
+ """
+ sig = 5
+ request = RegisterRequest()
+ request.event = sig
+ request.port = 8091
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(EventsServerService_Stub, port, 'localhost')
+ complist = server.registered_components
+ 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.')
+
+ def test_component_request_register(self):
+ """
+ Ensure components can register themselves with server.
+ """
+ sig = 6
+ complist = server.registered_components
+ self.assertTrue(sig not in complist,
+ 'There should be no registered components 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.')
+ self.assertTrue(port in complist[sig],
+ 'Failed registering component port.')
+
+ def test_component_receives_signal(self):
+ """
+ Ensure components can receive signals.
+ """
+ sig = 7
+
+ def getsig(param=None):
+ global received
+ received = True
+
+ events.register(sig, getsig)
+ request = SignalRequest()
+ request.event = sig
+ request.content = ""
+ request.mac_method = mac_auth.MacMethod.MAC_NONE
+ request.mac = ""
+ service = RpcService(EventsServerService_Stub, port, 'localhost')
+ 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.')
+
+ def test_component_send_signal(self):
+ """
+ Ensure components can send signals.
+ """
+ sig = 8
+ response = events.signal(sig)
+ self.assertTrue(response.status == response.OK,
+ 'Received wrong response status when signaling.')
+
+ def test_component_unregister_all(self):
+ """
+ Test that the component can unregister all events for one signal.
+ """
+ sig = CLIENT_UID
+ complist = server.registered_components
+ events.register(sig, lambda x: True)
+ events.register(sig, lambda x: True)
+ time.sleep(0.1)
+ events.unregister(sig)
+ time.sleep(0.1)
+ port = component.EventsComponentDaemon.get_instance().get_port()
+ self.assertFalse(bool(complist[sig]))
+ self.assertTrue(port not in complist[sig])
+
+ def test_component_unregister_by_uid(self):
+ """
+ Test that the component can unregister an event by uid.
+ """
+ sig = CLIENT_UID
+ complist = server.registered_components
+ 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 = component.EventsComponentDaemon.get_instance().get_port()
+ self.assertTrue(sig in complist)
+ self.assertTrue(len(complist[sig]) == 1)
+ self.assertTrue(
+ component.registered_callbacks[sig].pop()[0] == 'cbkuid2')
+ self.assertTrue(port in complist[sig])