
- Add PEP8 section to tox.ini - Add hacking to requirements to enforce OpenStack style requirements - Fix formatting issues flagged by flake8 check - Add copyright notices to all remaining files - Update .gitignore file Change-Id: I7e9a0203ddf2002c08ac96800fe30c1c46ebba88
191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
# Copyright (c) 2014 Dark Secret Software Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import struct
|
|
|
|
|
|
class InvalidVersion(Exception):
|
|
pass
|
|
|
|
|
|
class OutOfSync(Exception):
|
|
pass
|
|
|
|
|
|
class EndOfFile(Exception):
|
|
pass
|
|
|
|
|
|
BOR_MAGIC_NUMBER = 0x69867884
|
|
|
|
|
|
class Version0(object):
|
|
# Preamble ... same for all versions.
|
|
# i = 0x69867884 (EVNT)
|
|
# h = version
|
|
|
|
def __init__(self):
|
|
self.preamble_schema = "ih"
|
|
self.preamble_size = struct.calcsize(self.preamble_schema)
|
|
|
|
def make_preamble(self, version):
|
|
return struct.pack(self.preamble_schema, BOR_MAGIC_NUMBER, version)
|
|
|
|
def _check_eof(self, expected, actual):
|
|
if actual < expected:
|
|
raise EndOfFile()
|
|
|
|
def load_preamble(self, file_handle):
|
|
raw = file_handle.read(self.preamble_size)
|
|
self._check_eof(self.preamble_size, len(raw))
|
|
header = struct.unpack(self.preamble_schema, raw)
|
|
if header[0] != BOR_MAGIC_NUMBER:
|
|
raise OutOfSync("Expected Beginning of Record marker")
|
|
return header[1]
|
|
|
|
|
|
class Version1(Version0):
|
|
# Version 1 SCHEMA
|
|
# ----------------
|
|
# i = metadata block length
|
|
# i = raw notification block length
|
|
# i = 0x00000000 EOR
|
|
|
|
# Metadata dict block
|
|
# i = number of strings (N) - key/value = 2 strings
|
|
# N * i = length of key followed by length of value
|
|
# N * (*s) = key followed by value
|
|
|
|
# Raw notification block
|
|
# i = length of raw data block
|
|
# *s = raw data
|
|
|
|
# EXAMPLE
|
|
# --------
|
|
# With above Event and Metadata
|
|
#
|
|
# Header schema: "iii"
|
|
# Metadata length: 119
|
|
# Raw notification length: 201
|
|
# Metadata = 6 strings (3 key-value pairs)
|
|
# Metadata schema: "iiiiiii6s14s10s31s10s20s"
|
|
# ------ key/value
|
|
# ------ key/value
|
|
# ----- key/value
|
|
# ------ length of the 6 strings
|
|
# - 12 entries (6 string sizes + 6 strings)
|
|
# Raw notification: "i197s"
|
|
# ---- json notification
|
|
# - 197
|
|
|
|
def __init__(self):
|
|
super(Version1, self).__init__()
|
|
self.header_schema = "iii"
|
|
self.header_size = struct.calcsize(self.header_schema)
|
|
|
|
def _encode(self, s):
|
|
if isinstance(s, unicode):
|
|
return s.encode('utf-8')
|
|
return s
|
|
|
|
def pack(self, notification, metadata):
|
|
nsize = len(notification)
|
|
raw_block_schema = "i%ds" % nsize
|
|
raw_block = struct.pack(raw_block_schema, nsize, notification)
|
|
|
|
metadata_items = ["i"] # appended with N "%ds"'s
|
|
metadata_values = [len(metadata) * 4] # [n]=key, [n+1]=value
|
|
for key, value in metadata.iteritems():
|
|
key = self._encode(key)
|
|
value = self._encode(value)
|
|
metadata_items.append("i")
|
|
metadata_items.append("i")
|
|
metadata_values.append(len(key))
|
|
metadata_values.append(len(value))
|
|
|
|
for key, value in metadata.iteritems():
|
|
key = self._encode(key)
|
|
value = self._encode(value)
|
|
metadata_items.append("%ds" % len(key))
|
|
metadata_values.append(key)
|
|
metadata_items.append("%ds" % len(value))
|
|
metadata_values.append(value)
|
|
metadata_schema = "".join(metadata_items)
|
|
|
|
metadata = struct.pack(metadata_schema, *metadata_values)
|
|
|
|
header = struct.pack(self.header_schema,
|
|
struct.calcsize(metadata_schema),
|
|
struct.calcsize(raw_block_schema), 0)
|
|
|
|
preamble = self.make_preamble(1)
|
|
return (preamble, header, metadata, raw_block)
|
|
|
|
def unpack(self, file_handle):
|
|
header_bytes = file_handle.read(self.header_size)
|
|
self._check_eof(self.header_size, len(header_bytes))
|
|
header = struct.unpack(self.header_schema, header_bytes)
|
|
|
|
if header[2] != 0:
|
|
raise OutOfSync("Didn't find 0 EOR marker.")
|
|
|
|
metadata_bytes = file_handle.read(header[0])
|
|
self._check_eof(header[0], len(metadata_bytes))
|
|
num_strings = struct.unpack_from("i", metadata_bytes)
|
|
offset = struct.calcsize("i")
|
|
lengths = num_strings[0] / 2
|
|
lengths_schema = "i" * lengths
|
|
key_value_sizes = struct.unpack_from(lengths_schema, metadata_bytes,
|
|
offset=offset)
|
|
key_value_schema_list = ["%ds" % sz for sz in key_value_sizes]
|
|
key_value_schema = "".join(key_value_schema_list)
|
|
offset += struct.calcsize(lengths_schema)
|
|
key_values = struct.unpack_from(key_value_schema, metadata_bytes,
|
|
offset=offset)
|
|
metadata = dict((key_values[n], key_values[n + 1])
|
|
for n in range(len(key_values))[::2])
|
|
|
|
raw = file_handle.read(header[1])
|
|
self._check_eof(header[1], len(raw))
|
|
raw_len = struct.unpack_from("i", raw)
|
|
offset = struct.calcsize("i")
|
|
jnot = struct.unpack_from("%ds" % raw_len[0], raw, offset=offset)
|
|
return (metadata, jnot[0])
|
|
|
|
|
|
VERSIONS = {1: Version1()}
|
|
CURRENT_VERSION = 1
|
|
|
|
|
|
def get_version_handler(version=CURRENT_VERSION):
|
|
global VERSIONS
|
|
|
|
version_handler = VERSIONS.get(version)
|
|
if not version_handler:
|
|
raise InvalidVersion()
|
|
return version_handler
|
|
|
|
|
|
def pack_notification(notification, metadata, version=CURRENT_VERSION):
|
|
version_handler = get_version_handler(version)
|
|
return version_handler.pack(notification, metadata)
|
|
|
|
|
|
def unpack_notification(file_handle):
|
|
v0 = Version0()
|
|
version = v0.load_preamble(file_handle)
|
|
version_handler = get_version_handler(version)
|
|
return version_handler.unpack(file_handle)
|