280 lines
7.1 KiB
Python
280 lines
7.1 KiB
Python
# passerelle - uniform access to multiple data sources and services
|
|
# Copyright (C) 2018 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import calendar
|
|
from datetime import datetime
|
|
|
|
ENCODE_TOKENS = {
|
|
'integer': 16,
|
|
'real': 19,
|
|
'nil': 0,
|
|
'true': 1,
|
|
'false': 2,
|
|
'emptyString': 3,
|
|
'emptyData': 4,
|
|
'ref': 9,
|
|
'i1': 10,
|
|
'u1': 11,
|
|
'i2': 12,
|
|
'u2': 13,
|
|
'i4': 14,
|
|
'uint32': 15,
|
|
'i8': 16,
|
|
'u8': 17,
|
|
'float': 18,
|
|
'double': 19,
|
|
'decimal': 20,
|
|
'string': 21,
|
|
'localdate': 22,
|
|
'gmtDate': 23,
|
|
'color': 24,
|
|
'data': 25,
|
|
'naturals': 26,
|
|
'dictionary': 30,
|
|
'array': 31,
|
|
'couple': 32,
|
|
'object': 50,
|
|
}
|
|
|
|
DECODE_TOKENS = {v: k for k, v in ENCODE_TOKENS.items()}
|
|
|
|
|
|
class Couple(list):
|
|
pass
|
|
|
|
|
|
class Uint32(int):
|
|
pass
|
|
|
|
|
|
class MSTEDecoder:
|
|
def __init__(self, data):
|
|
self._idx = 4
|
|
self._keys = []
|
|
self._refs = []
|
|
self._data = data
|
|
|
|
def _next_token(self):
|
|
self._idx += 1
|
|
return self._data[self._idx]
|
|
|
|
def _parse_array(self):
|
|
count = self._next_token()
|
|
res = []
|
|
self._refs.append(res)
|
|
while count > 0:
|
|
res.append(self._parse_item())
|
|
count -= 1
|
|
return res
|
|
|
|
def _parse_couple(self):
|
|
res = Couple()
|
|
self._refs.append(res)
|
|
for _ in range(2):
|
|
res.append(self._parse_item())
|
|
return res
|
|
|
|
def _parse_decimal(self):
|
|
res = float(self._next_token())
|
|
self._refs.append(res)
|
|
return res
|
|
|
|
def _parse_dictionary(self):
|
|
count = self._next_token()
|
|
res = {}
|
|
self._refs.append(res)
|
|
while count > 0:
|
|
key = self._keys[self._next_token()]
|
|
res[key] = self._parse_item()
|
|
count -= 1
|
|
return res
|
|
|
|
def _parse_emptyString(self):
|
|
return ''
|
|
|
|
def _parse_gmtDate(self):
|
|
return self._parse_localdate()
|
|
|
|
def _parse_item(self):
|
|
token = self._next_token()
|
|
_type = DECODE_TOKENS[token]
|
|
return getattr(self, "_parse_%s" % _type)()
|
|
|
|
def _parse_localdate(self):
|
|
timestamp = self._next_token()
|
|
res = datetime.fromtimestamp(timestamp)
|
|
self._refs.append(res)
|
|
return res
|
|
|
|
def _parse_nil(self):
|
|
return None
|
|
|
|
def _parse_ref(self):
|
|
pos = self._next_token()
|
|
return self._refs[pos]
|
|
|
|
def _parse_string(self):
|
|
res = self._next_token()
|
|
self._refs.append(res)
|
|
return res
|
|
|
|
def _parse_true(self):
|
|
return True
|
|
|
|
def _parse_false(self):
|
|
return False
|
|
|
|
def _parse_uint32(self):
|
|
return int(self._next_token())
|
|
|
|
def decode(self):
|
|
num_keys = self._data[self._idx]
|
|
while num_keys > 0:
|
|
self._keys.append(self._next_token())
|
|
num_keys -= 1
|
|
return self._parse_item()
|
|
|
|
|
|
class ObjectStore(list):
|
|
def add(self, obj):
|
|
"""Add object in the store
|
|
and return its reference
|
|
"""
|
|
ref = self.getref(obj)
|
|
if ref is None:
|
|
self.append(obj)
|
|
ref = len(self) - 1
|
|
return ref
|
|
|
|
def getref(self, obj):
|
|
"""Return the reference of obj,
|
|
None if the object is not in the store
|
|
"""
|
|
try:
|
|
return self.index(obj)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
class MSTEEncoder:
|
|
def __init__(self, data):
|
|
self._data = data
|
|
self._stream = []
|
|
self._refs_store = ObjectStore()
|
|
self._keys_store = ObjectStore()
|
|
|
|
def _push_token_type(self, token_type):
|
|
self._stream.append(ENCODE_TOKENS[token_type])
|
|
|
|
def _push(self, item):
|
|
self._stream.append(item)
|
|
|
|
def _encode_array(self, obj):
|
|
self._refs_store.add(obj)
|
|
self._push_token_type('array')
|
|
self._push(len(obj))
|
|
for elem in obj:
|
|
self._encode_obj(elem)
|
|
|
|
def _encode_boolean(self, obj):
|
|
if obj:
|
|
self._push_token_type('true')
|
|
else:
|
|
self._push_token_type('false')
|
|
|
|
def _encode_couple(self, obj):
|
|
self._refs_store.add(obj)
|
|
self._push_token_type('couple')
|
|
for elem in obj:
|
|
self._encode_obj(elem)
|
|
|
|
def _encode_decimal(self, obj):
|
|
self._refs_store.add(obj)
|
|
self._push_token_type('decimal')
|
|
self._push(int(obj))
|
|
|
|
def _encode_dictionary(self, obj):
|
|
self._refs_store.add(obj)
|
|
self._push_token_type('dictionary')
|
|
self._push(len(obj))
|
|
for key, value in obj.items():
|
|
key_ref = self._keys_store.add(key)
|
|
self._push(key_ref)
|
|
self._encode_obj(value)
|
|
|
|
def _encode_localdate(self, obj):
|
|
# obj must be utc
|
|
self._refs_store.add(obj)
|
|
self._push_token_type('localdate')
|
|
self._push(int(calendar.timegm(obj.timetuple())))
|
|
|
|
def _encode_obj(self, obj):
|
|
ref = self._refs_store.getref(obj)
|
|
if ref is not None:
|
|
self._push_token_type('ref')
|
|
self._push(ref)
|
|
elif isinstance(obj, str):
|
|
self._encode_string(obj)
|
|
elif obj is None:
|
|
self._encode_nil()
|
|
elif isinstance(obj, Couple):
|
|
self._encode_couple(obj)
|
|
elif isinstance(obj, bool):
|
|
self._encode_boolean(obj)
|
|
elif isinstance(obj, list):
|
|
self._encode_array(obj)
|
|
elif isinstance(obj, dict):
|
|
self._encode_dictionary(obj)
|
|
elif isinstance(obj, float):
|
|
self._encode_decimal(obj)
|
|
elif isinstance(obj, datetime):
|
|
self._encode_localdate(obj)
|
|
elif isinstance(obj, Uint32):
|
|
self._encode_uint32(obj)
|
|
else:
|
|
raise TypeError("%s encoding not supported" % type(obj))
|
|
|
|
def _encode_nil(self):
|
|
self._push_token_type('nil')
|
|
|
|
def _encode_string(self, _str):
|
|
if _str:
|
|
self._push_token_type('string')
|
|
self._push(_str)
|
|
self._refs_store.add(_str)
|
|
else:
|
|
self._push_token_type('emptyString')
|
|
|
|
def _encode_uint32(self, obj):
|
|
self._push_token_type('uint32')
|
|
self._push(obj)
|
|
|
|
def encode(self):
|
|
res = ["MSTE0102"]
|
|
self._encode_obj(self._data)
|
|
nb_token = 5 + len(self._keys_store) + len(self._stream)
|
|
res = ["MSTE0102", nb_token, 'CRC00000000', 0, len(self._keys_store)] + self._keys_store
|
|
res.extend(self._stream)
|
|
return res
|
|
|
|
|
|
def decode(data):
|
|
return MSTEDecoder(data).decode()
|
|
|
|
|
|
def encode(data):
|
|
return MSTEEncoder(data).encode()
|