vici: Add vici python protocol handler
authorBjörn Schuberg <bjorn.schuberg@gmail.com>
Sat, 14 Feb 2015 11:54:31 +0000 (12:54 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 18 Mar 2015 12:59:13 +0000 (13:59 +0100)
src/libcharon/plugins/vici/python/.gitignore [new file with mode: 0644]
src/libcharon/plugins/vici/python/vici/__init__.py [new file with mode: 0644]
src/libcharon/plugins/vici/python/vici/exception.py [new file with mode: 0644]
src/libcharon/plugins/vici/python/vici/protocol.py [new file with mode: 0644]

diff --git a/src/libcharon/plugins/vici/python/.gitignore b/src/libcharon/plugins/vici/python/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/src/libcharon/plugins/vici/python/vici/__init__.py b/src/libcharon/plugins/vici/python/vici/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/libcharon/plugins/vici/python/vici/exception.py b/src/libcharon/plugins/vici/python/vici/exception.py
new file mode 100644 (file)
index 0000000..25d7349
--- /dev/null
@@ -0,0 +1,4 @@
+"""Exception types that may be thrown by this library."""
+
+class DeserializationException(Exception):
+    """Encountered an unexpected byte sequence or missing element type."""
\ No newline at end of file
diff --git a/src/libcharon/plugins/vici/python/vici/protocol.py b/src/libcharon/plugins/vici/python/vici/protocol.py
new file mode 100644 (file)
index 0000000..fe4e5d7
--- /dev/null
@@ -0,0 +1,194 @@
+import io
+import socket
+import struct
+
+from collections import namedtuple
+
+from .exception import DeserializationException
+
+
+class Transport(object):
+    HEADER_LENGTH = 4
+    MAX_SEGMENT = 512 * 1024
+
+    def __init__(self, address="/var/run/charon.vici"):
+        self.address = address
+        self.socket = socket.socket(socket.AF_UNIX)
+        self.socket.connect(address)
+
+    def send(self, packet):
+        self.socket.sendall(struct.pack("!I", len(packet)) + packet)
+
+    def receive(self):
+        raw_length = self.socket.recv(self.HEADER_LENGTH)
+        length, = struct.unpack("!I", raw_length)
+        payload = self.socket.recv(length)
+        return payload
+
+    def close(self):
+        self.socket.shutdown(socket.SHUT_RDWR)
+        self.socket.close()
+
+
+class Packet(object):
+    CMD_REQUEST = 0         # Named request message
+    CMD_RESPONSE = 1        # Unnamed response message for a request
+    CMD_UNKNOWN = 2         # Unnamed response if requested command is unknown
+    EVENT_REGISTER = 3      # Named event registration request
+    EVENT_UNREGISTER = 4    # Named event de-registration request
+    EVENT_CONFIRM = 5       # Unnamed confirmation for event (de-)registration
+    EVENT_UNKNOWN = 6       # Unnamed response if event (de-)registration failed
+    EVENT = 7               # Named event message
+
+    ParsedPacket = namedtuple(
+        "ParsedPacket",
+        ["response_type", "payload"]
+    )
+
+    ParsedEventPacket = namedtuple(
+        "ParsedEventPacket",
+        ["response_type", "event_type", "payload"]
+    )
+
+    @classmethod
+    def _named_request(cls, request_type, request, message=None):
+        payload = struct.pack("!BB", request_type, len(request)) + request
+        if message is not None:
+            return payload + message
+        else:
+            return payload
+
+    @classmethod
+    def request(cls, command, message=None):
+        return cls._named_request(cls.CMD_REQUEST, command, message)
+
+    @classmethod
+    def register_event(cls, event_type):
+        return cls._named_request(cls.EVENT_REGISTER, event_type)
+
+    @classmethod
+    def unregister_event(cls, event_type):
+        return cls._named_request(cls.EVENT_UNREGISTER, event_type)
+
+    @classmethod
+    def parse(cls, packet):
+        stream = FiniteStream(packet)
+        response_type, = struct.unpack("!B", stream.read(1))
+
+        if response_type == cls.EVENT:
+            length, = struct.unpack("!B", stream.read(1))
+            event_type = stream.read(length)
+            return cls.ParsedEventPacket(response_type, event_type, stream)
+        else:
+            return cls.ParsedPacket(response_type, stream)
+
+
+class Message(object):
+    SECTION_START = 1       # Begin a new section having a name
+    SECTION_END = 2         # End a previously started section
+    KEY_VALUE = 3           # Define a value for a named key in the section
+    LIST_START = 4          # Begin a named list for list items
+    LIST_ITEM = 5           # Define an unnamed item value in the current list
+    LIST_END = 6            # End a previously started list
+
+    @classmethod
+    def serialize(cls, message):
+        def encode_named_type(marker, name):
+            name = str(name)
+            return struct.pack("!BB", marker, len(name)) + name
+
+        def encode_blob(value):
+            value = str(value)
+            return struct.pack("!H", len(value)) + value
+
+        def serialize_list(lst):
+            segment = str()
+            for item in lst:
+                segment += struct.pack("!B", cls.LIST_ITEM) + encode_blob(item)
+            return segment
+
+        def serialize_dict(d):
+            segment = str()
+            for key, value in d.iteritems():
+                if isinstance(value, dict):
+                    segment += (
+                        encode_named_type(cls.SECTION_START, key)
+                        + serialize_dict(value)
+                        + struct.pack("!B", cls.SECTION_END)
+                    )
+                elif isinstance(value, list):
+                    segment += (
+                        encode_named_type(cls.LIST_START, key)
+                        + serialize_list(value)
+                        + struct.pack("!B", cls.LIST_END)
+                    )
+                else:
+                    segment += (
+                        encode_named_type(cls.KEY_VALUE, key)
+                        + encode_blob(value)
+                    )
+            return segment
+
+        return serialize_dict(message)
+
+    @classmethod
+    def deserialize(cls, stream):
+        def decode_named_type(stream):
+            length, = struct.unpack("!B", stream.read(1))
+            return stream.read(length)
+
+        def decode_blob(stream):
+            length, = struct.unpack("!H", stream.read(2))
+            return stream.read(length)
+
+        def decode_list_item(stream):
+            marker, = struct.unpack("!B", stream.read(1))
+            while marker == cls.LIST_ITEM:
+                yield decode_blob(stream)
+                marker, = struct.unpack("!B", stream.read(1))
+
+            if marker != cls.LIST_END:
+                raise DeserializationException(
+                    "Expected end of list at {pos}".format(pos=stream.tell())
+                )
+
+        section = {}
+        section_stack = []
+        while stream.has_more():
+            element_type, = struct.unpack("!B", stream.read(1))
+            if element_type == cls.SECTION_START:
+                section_name = decode_named_type(stream)
+                new_section = {}
+                section[section_name] = new_section
+                section_stack.append(section)
+                section = new_section
+
+            elif element_type == cls.LIST_START:
+                list_name = decode_named_type(stream)
+                section[list_name] = [item for item in decode_list_item(stream)]
+
+            elif element_type == cls.KEY_VALUE:
+                key = decode_named_type(stream)
+                section[key] = decode_blob(stream)
+
+            elif element_type == cls.SECTION_END:
+                if len(section_stack):
+                    section = section_stack.pop()
+                else:
+                    raise DeserializationException(
+                        "Unexpected end of section at {pos}".format(
+                            pos=stream.tell()
+                        )
+                    )
+
+        if len(section_stack):
+            raise DeserializationException("Expected end of section")
+        return section
+
+
+class FiniteStream(io.BytesIO):
+    def __len__(self):
+        return len(self.getvalue())
+
+    def has_more(self):
+        return self.tell() < len(self)
\ No newline at end of file