From 7c05570f7a2c7baeecf9fa9c9e9f47acb39b56bd Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Sat, 10 May 2014 11:36:53 +0000 Subject: Initial commit. dht.get_start, dht.start and gns.lookup work --- example-dht.py | 22 +++++++++++ example-gns.py | 15 ++++++++ gnunet/__init__.py | 31 +++++++++++++++ gnunet/_dbus_utils.py | 65 +++++++++++++++++++++++++++++++ gnunet/block.py | 16 ++++++++ gnunet/crypto.py | 10 +++++ gnunet/dht.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ gnunet/gns.py | 48 +++++++++++++++++++++++ gnunet/gnsrecord.py | 36 +++++++++++++++++ gnunet/strings.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 445 insertions(+) create mode 100755 example-dht.py create mode 100755 example-gns.py create mode 100644 gnunet/__init__.py create mode 100644 gnunet/_dbus_utils.py create mode 100644 gnunet/block.py create mode 100644 gnunet/crypto.py create mode 100644 gnunet/dht.py create mode 100644 gnunet/gns.py create mode 100644 gnunet/gnsrecord.py create mode 100644 gnunet/strings.py diff --git a/example-dht.py b/example-dht.py new file mode 100755 index 0000000..726e0be --- /dev/null +++ b/example-dht.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +import gnunet.dht +import time + +key = gnunet.HashCode("RMKN0U1JNA3PVCL148D6JI0STVG94A8A65INOK849CF1RT6BGF26AMMT14GMDMNRDFSJRJME6IKJ3LDFBUL2R1TPQJE64I55I32QN5G") + +gnunet.dht.put(key, 1, "test", b"hello") + +def result_callback(block_type, key, data, expiry, get_path, put_path): + print("Got result from DHT") + print(" block_type == %s" % repr(block_type)) + print(" key == %s" % repr(key)) + print(" expiry == %s" % repr(expiry)) + print(" get_path == %s" % repr(get_path)) + print(" put_path == %s" % repr(put_path)) + print(" data == %s" % repr(data)) + +gnunet.dht.get_start(result_callback, "test", key, 1, record_route=True) + +time.sleep(1) + diff --git a/example-gns.py b/example-gns.py new file mode 100755 index 0000000..4b3ea10 --- /dev/null +++ b/example-gns.py @@ -0,0 +1,15 @@ +#!/usr/bin/python3 + +import gnunet.gns + +results = gnunet.gns.lookup("www.gnu", "JK55QA8JLAL64MBO8UM209KE93M9JBBO7M2UB8M3M03FKRFSUOMG", "A", True) + +for r in results: + print("Got result from gns") + print(" record_type == %s" % repr(r.record_type)) + print(" data == %s" % repr(r.data)) + print(" expiration_time == %s" % repr(r.expiration_time)) + print(" private == %s" % repr(r.private)) + print(" pending == %s" % repr(r.pending)) + print(" shadow == %s" % repr(r.shadow)) + diff --git a/gnunet/__init__.py b/gnunet/__init__.py new file mode 100644 index 0000000..425ab86 --- /dev/null +++ b/gnunet/__init__.py @@ -0,0 +1,31 @@ +import gnunet.strings as strings + +class GNUNetDaemonError(Exception): + pass + +class _Key: + def __init__(self, arg, subtype, bits): + if isinstance(arg, subtype): + self._data = arg.data + elif isinstance(arg, str): + self._data = strings.string_to_data(arg) + else: + try: + self._data = bytearray(arg) + except: + raise TypeError("'arg' must be a " + type(subtype).__name__ + ", a string or an array of bytes. Not a '" + type(arg).__name__ + "'.") + + if len(self._data) * 8 != bits: + raise ValueError("'arg' must be a " + bits + " bit hash. Got " + len(self._data) + " bits.") + + def __str__(self): + return strings.data_to_string(self._data) + + +class HashCode(_Key): + def __init__(self, arg): + _Key.__init__(self, arg, HashCode, 512) + + def __repr__(self): + return "gnunet.HashCode('" + str(self) + "')" + diff --git a/gnunet/_dbus_utils.py b/gnunet/_dbus_utils.py new file mode 100644 index 0000000..4e08c2a --- /dev/null +++ b/gnunet/_dbus_utils.py @@ -0,0 +1,65 @@ +import dbus +import threading +import datetime +from gi.repository import Gtk + +from dbus.mainloop.glib import DBusGMainLoop, threads_init +threads_init() +DBusGMainLoop(set_as_default=True) +sysbus = dbus.SystemBus() + +class MainLoop(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.daemon = True + self.start() + + def run(self): + Gtk.main() + +MainLoop() + +from gnunet import _Key, GNUNetDaemonError +import gnunet.strings as strings + +def pythonize(arg, argtype): + if argtype is datetime.datetime: + if isinstance(arg, str): + return strings.string_to_absolute_time(arg) + if isinstance(arg. dbus.UInt64): + return datetime.datetime(1970, 1, 1) + datetime.timedelta(microseconds=arg) + return datatime.datetime(arg) + +def dbusize(arg, pretty): + if isinstance(arg, _Key): + if pretty: + return dbus.String(arg, variant_level=1) + else: + return dbus.Array(arg._data[:], variant_level=1, signature="y") + + #if type(arg) is gnsrecord.Data: + #return dbus.Struct([arg._recordtype, + + if isinstance(arg, datetime.datetime): + if pretty: + return dbus.String(strings.absolute_time_to_string(arg), variant_level=1) + else: + return dbus.UInt64((arg - datetime.datetime(1970, 1, 1)).total_seconds() * 1000000, variant_level=1) + +def handle_exception(e, daemon, daemon_address): + name = e.get_dbus_name() + message = e.get_dbus_message() + if not name.startswith("org.freedesktop.DBus.Error."): + raise e + name = name[len("org.freedesktop.DBus.Error."):] + + if name == "Failed" or name == "InvalidArgs": + raise GNUNetDaemonError(message) + if name == "NoMemory": + raise MemoryError(message) + if name == "ServiceUnknown" or name == "NameHasNoOwner": + raise GNUNetDaemonError("Failed to contact " + daemon + " daemon at " + daemon_address) + if name == "NoReply" or name == "Timeout": + raise GNUNetDaemonError("Did not receive reply from " + daemon + " daemon at " + daemon_address + ". Daemon might of crashed") + raise e + diff --git a/gnunet/block.py b/gnunet/block.py new file mode 100644 index 0000000..2a2dfec --- /dev/null +++ b/gnunet/block.py @@ -0,0 +1,16 @@ +types = set([ + "any", + "fs_dblock", + "fs_iblock", + "fs_kblock", + "fs_sblock", + "fs_nblock", + "fs_ondemand", + "dht_hello", + "test", + "fs_ublock", + "dns", + "gns_namerecord", + "regex", + "regex_accept"]) + diff --git a/gnunet/crypto.py b/gnunet/crypto.py new file mode 100644 index 0000000..b81b49d --- /dev/null +++ b/gnunet/crypto.py @@ -0,0 +1,10 @@ +from gnunet import _Key +import gnunet.strings as strings + +class EcdsaPublicKey(_Key): + def __init__(self, arg): + _Key.__init__(self, arg, EcdsaPublicKey, 256) + + def __repr__(self): + return "gnunet.crypto.EcdsaPublicKey('" + str(self) + "')" + diff --git a/gnunet/dht.py b/gnunet/dht.py new file mode 100644 index 0000000..d26b7f2 --- /dev/null +++ b/gnunet/dht.py @@ -0,0 +1,105 @@ +import dbus + +import datetime + +from gnunet import * +from gnunet._dbus_utils import * + +import gnunet.block as block + +get_requests = {} +requests_lock = threading.Lock() + +class GetResult(threading.Thread): + def __init__(self, expiry, key, get_path, put_path, block_type, data, path): + threading.Thread.__init__(self) + self.expiry = expiry + self.key = key + self.get_path = get_path + self.put_path = put_path + self.block_type = block_type + self.data = data + self.path = path + self.daemon = True + self.start() + + def run(self): + request = None + with requests_lock: + request = get_requests[self.path] + + if request: + if request.record_route: + request.callback(self.block_type, self.key, self.data, self.expiry, get_path=self.get_path, put_path=self.put_path) + else: + request.callback(self.block_type, self.key, self.data, self.expiry) + +def _result(expiry, key, get_path, put_path, block_type, data, path): + expiry = pythonize(expiry, datetime.datetime) + key = HashCode(key) + get_path = list(get_path) + put_path = list(put_path) + block_type = str(block_type) + data = bytearray(data) + GetResult(expiry, key, get_path, put_path, block_type, data, path) + +sysbus.add_signal_receiver(_result, "result", "gnu.gnunet.dht.get", "gnu.gnunet.dht", path_keyword="path") + +class GetRequest: + def __init__(self, path, callback, record_route): + self._path = path + self.callback = callback + self.record_route = record_route + +def put(key, desired_replication_level, block_type, data, expiry=None, demultiplex_everywhere=False, record_route=False, bart=False): + key = dbusize(HashCode(key), True) + desired_replication_level = dbus.UInt32(desired_replication_level) + if block_type not in block.types: + raise ValueError("'block_type' must be one of %s" % block.types) + block_type = dbus.String(block_type, variant_level=1) + if expiry is not None: + if not isinstance(expiry, datetime.datetime): + raise TypeError("'expiry' must be a datetime.datetime") + expiry = dbusize(expiry) + else: + expiry = dbus.String("end of time", variant_level=1) + options = dbus.Array([], variant_level=1, signature="s") + if demultiplex_everywhere: + options += ["demultiplex_everywhere"] + if record_route: + options += ["record_route"] + if bart: + options += ["bart"] + data = dbus.Array(bytearray(data), signature="y") + + try: + sysbus.get_object("gnu.gnunet.dht", "/").put(key, desired_replication_level, options, block_type, data, expiry) + except dbus.DBusException as e: + handle_exception(e, "dht", "gnu.gnunet.dht") + +def get_start(callback, block_type, key, desired_replication_level, demultiplex_everywhere=False, record_route=False, bart=False): + if block_type not in block.types: + raise ValueError("'block_type' must be one of %s" % block.types) + block_type = dbus.String(block_type, variant_level=1) + key = dbusize(HashCode(key), True) + desired_replication_level = dbus.UInt32(desired_replication_level) + options = dbus.Array([], variant_level=1, signature="s") + if demultiplex_everywhere: + options += ["demultiplex_everywhere"] + if record_route: + options += ["record_route"] + if bart: + options += ["bart"] + + ret = None + try: + with requests_lock: + path = sysbus.get_object("gnu.gnunet.dht", "/").get_start(block_type, key, desired_replication_level, options) + ret = GetRequest(path, callback, record_route) + get_requests[path] = ret + except dbus.DBusException as e: + handle_exception(e, "dht", "gnu.gnunet.dht") + + return ret + + diff --git a/gnunet/gns.py b/gnunet/gns.py new file mode 100644 index 0000000..57c1bd9 --- /dev/null +++ b/gnunet/gns.py @@ -0,0 +1,48 @@ +import dbus + +from gnunet._dbus_utils import * + +from gnunet import * +import gnunet.crypto as crypto +import gnunet.gnsrecord as gnsrecord + +def lookup(name, zone, record_type, only_cached): + name = str(name) + zone = dbusize(crypto.EcdsaPublicKey(zone), True) + if record_type not in gnsrecord.types: + raise ValueError("'record_type' must be one of %s" % gnsrecord.types) + #record_type = dbus.UInt32(gnsrecord.types[record_type], variant_level=1) + record_type = dbus.String(record_type, variant_level=1) + only_cached = dbus.Boolean(only_cached) + + try: + results = sysbus.get_object("gnu.gnunet.gns", "/").lookup(name, zone, record_type, only_cached) + except dbus.DBusException as e: + handle_exception(e, "gns", "gnu.gnunet.gns") + + ret = [] + for r in results: + record_type = str(r[0]) + private = False + pending = False + shadow = False + relative = False + for f in r[1]: + if f == "private": + private = True + if f == "pending": + pending = True + if f == "shadow": + shadow = True + if f == "relative_expiration": + relative = True + data = str(r[2]) + expiration_time = None + if relative: + expiration_time = pythonize(r[3], datetime.timedelta) + else: + expiration_time = pythonize(r[3], datetime.datetime) + ret.append(gnsrecord.Data(record_type, data, expiration_time, private, pending, shadow)) + + return ret + diff --git a/gnunet/gnsrecord.py b/gnunet/gnsrecord.py new file mode 100644 index 0000000..4d00139 --- /dev/null +++ b/gnunet/gnsrecord.py @@ -0,0 +1,36 @@ +import datetime + +dns_types = { + "A": 1, + "NS": 2, + "CNAME": 5, + "SOA": 6, + "PTR": 12, + "MX": 15, + "TXT": 16, + "AAAA": 28, + "TLSA": 52} + +gns_types = { + "PKEY": 65536, + "NICK": 65537, + "LEHO": 65538, + "VPN": 65539, + "GNS2DNS": 65540} + +types = dict(list(dns_types.items()) + list(gns_types.items())) + +class Data: + def __init__(self, record_type, data, expiration_time=None, private=None, pending=None, shadow=None): + self.record_type = str(record_type) + if record_type not in types: + raise ValueError("'record_type' must be one of %s" % types) + #self.data = bytearray(data) + self.data = str(data) + if expiration_time is not None and not isinstance(expiration_time, datetime.datetime) or isinstance(expiration_time, datetime.timedelta): + raise TypeError("'expiration_time' must be a datetime.datetime or a datetime.timedelta") + self.expiration_time = expiration_time + self.private = private + self.pending = pending + self.shadow = shadow + diff --git a/gnunet/strings.py b/gnunet/strings.py new file mode 100644 index 0000000..cfbdaa5 --- /dev/null +++ b/gnunet/strings.py @@ -0,0 +1,97 @@ +import datetime + +from gnunet import * + +encTable = "0123456789ABCDEFGHIJKLMNOPQRSTUV" + +def data_to_string(data): + data = bytearray(data) + size = len(data) + bits = 0 + rpos = 0 + vbit = 0 + ret = "" + while rpos < size: + while rpos < size and vbit < 5: + bits = (bits << 8) | data[rpos] + rpos += 1 + vbit += 8 + while vbit >= 5: + vbit -= 5 + ret += encTable[(bits >> vbit) & 31] + if vbit > 0: + ret += encTable[(bits << (5 - vbit)) & 31] + return ret + +def string_to_data(s): + s = str(s) + size = len(s) + bits = 0 + rpos = 0 + vbit = 0 + ret = bytearray([]) + try: + while rpos < size: + while rpos < size and vbit < 8: + bits = (bits << 5) | int(s[rpos], 32) + rpos += 1 + vbit += 5 + while vbit >= 8: + vbit -= 8 + ret.append((bits >> vbit) & 255) + if vbit > 0: + if bits & ((1 << vbit) - 1) != 0: + raise ValueError("") + except ValueError: + raise ValueError("'" + s + "' is not a valid data-encoding string") + return ret + +def absolute_time_to_string(t): + return t.strftime("%a %b %d %H:%M:%S %Y") + +def string_to_absolute_time(s): + if s == "end of time": + return None + try: + return datetime.datetime.strptime(s, "%a %b %d %H:%M:%S %Y") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%c") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Ec") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%x") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Ex") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Y-%m-%d") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Y-%m") + except ValueError: + pass + try: + return datetime.datetime.strptime(s, "%Y") + except ValueError: + pass + raise ValueError("%s is not a properly formatted time string" % s) + + -- cgit v1.2.3