diff options
-rwxr-xr-x | example-dht.py | 22 | ||||
-rwxr-xr-x | example-gns.py | 15 | ||||
-rw-r--r-- | gnunet/__init__.py | 31 | ||||
-rw-r--r-- | gnunet/_dbus_utils.py | 65 | ||||
-rw-r--r-- | gnunet/block.py | 16 | ||||
-rw-r--r-- | gnunet/crypto.py | 10 | ||||
-rw-r--r-- | gnunet/dht.py | 105 | ||||
-rw-r--r-- | gnunet/gns.py | 48 | ||||
-rw-r--r-- | gnunet/gnsrecord.py | 36 | ||||
-rw-r--r-- | gnunet/strings.py | 97 |
10 files changed, 445 insertions, 0 deletions
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 @@ | |||
1 | #!/usr/bin/python3 | ||
2 | |||
3 | import gnunet.dht | ||
4 | import time | ||
5 | |||
6 | key = gnunet.HashCode("RMKN0U1JNA3PVCL148D6JI0STVG94A8A65INOK849CF1RT6BGF26AMMT14GMDMNRDFSJRJME6IKJ3LDFBUL2R1TPQJE64I55I32QN5G") | ||
7 | |||
8 | gnunet.dht.put(key, 1, "test", b"hello") | ||
9 | |||
10 | def result_callback(block_type, key, data, expiry, get_path, put_path): | ||
11 | print("Got result from DHT") | ||
12 | print(" block_type == %s" % repr(block_type)) | ||
13 | print(" key == %s" % repr(key)) | ||
14 | print(" expiry == %s" % repr(expiry)) | ||
15 | print(" get_path == %s" % repr(get_path)) | ||
16 | print(" put_path == %s" % repr(put_path)) | ||
17 | print(" data == %s" % repr(data)) | ||
18 | |||
19 | gnunet.dht.get_start(result_callback, "test", key, 1, record_route=True) | ||
20 | |||
21 | time.sleep(1) | ||
22 | |||
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 @@ | |||
1 | #!/usr/bin/python3 | ||
2 | |||
3 | import gnunet.gns | ||
4 | |||
5 | results = gnunet.gns.lookup("www.gnu", "JK55QA8JLAL64MBO8UM209KE93M9JBBO7M2UB8M3M03FKRFSUOMG", "A", True) | ||
6 | |||
7 | for r in results: | ||
8 | print("Got result from gns") | ||
9 | print(" record_type == %s" % repr(r.record_type)) | ||
10 | print(" data == %s" % repr(r.data)) | ||
11 | print(" expiration_time == %s" % repr(r.expiration_time)) | ||
12 | print(" private == %s" % repr(r.private)) | ||
13 | print(" pending == %s" % repr(r.pending)) | ||
14 | print(" shadow == %s" % repr(r.shadow)) | ||
15 | |||
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 @@ | |||
1 | import gnunet.strings as strings | ||
2 | |||
3 | class GNUNetDaemonError(Exception): | ||
4 | pass | ||
5 | |||
6 | class _Key: | ||
7 | def __init__(self, arg, subtype, bits): | ||
8 | if isinstance(arg, subtype): | ||
9 | self._data = arg.data | ||
10 | elif isinstance(arg, str): | ||
11 | self._data = strings.string_to_data(arg) | ||
12 | else: | ||
13 | try: | ||
14 | self._data = bytearray(arg) | ||
15 | except: | ||
16 | raise TypeError("'arg' must be a " + type(subtype).__name__ + ", a string or an array of bytes. Not a '" + type(arg).__name__ + "'.") | ||
17 | |||
18 | if len(self._data) * 8 != bits: | ||
19 | raise ValueError("'arg' must be a " + bits + " bit hash. Got " + len(self._data) + " bits.") | ||
20 | |||
21 | def __str__(self): | ||
22 | return strings.data_to_string(self._data) | ||
23 | |||
24 | |||
25 | class HashCode(_Key): | ||
26 | def __init__(self, arg): | ||
27 | _Key.__init__(self, arg, HashCode, 512) | ||
28 | |||
29 | def __repr__(self): | ||
30 | return "gnunet.HashCode('" + str(self) + "')" | ||
31 | |||
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 @@ | |||
1 | import dbus | ||
2 | import threading | ||
3 | import datetime | ||
4 | from gi.repository import Gtk | ||
5 | |||
6 | from dbus.mainloop.glib import DBusGMainLoop, threads_init | ||
7 | threads_init() | ||
8 | DBusGMainLoop(set_as_default=True) | ||
9 | sysbus = dbus.SystemBus() | ||
10 | |||
11 | class MainLoop(threading.Thread): | ||
12 | def __init__(self): | ||
13 | threading.Thread.__init__(self) | ||
14 | self.daemon = True | ||
15 | self.start() | ||
16 | |||
17 | def run(self): | ||
18 | Gtk.main() | ||
19 | |||
20 | MainLoop() | ||
21 | |||
22 | from gnunet import _Key, GNUNetDaemonError | ||
23 | import gnunet.strings as strings | ||
24 | |||
25 | def pythonize(arg, argtype): | ||
26 | if argtype is datetime.datetime: | ||
27 | if isinstance(arg, str): | ||
28 | return strings.string_to_absolute_time(arg) | ||
29 | if isinstance(arg. dbus.UInt64): | ||
30 | return datetime.datetime(1970, 1, 1) + datetime.timedelta(microseconds=arg) | ||
31 | return datatime.datetime(arg) | ||
32 | |||
33 | def dbusize(arg, pretty): | ||
34 | if isinstance(arg, _Key): | ||
35 | if pretty: | ||
36 | return dbus.String(arg, variant_level=1) | ||
37 | else: | ||
38 | return dbus.Array(arg._data[:], variant_level=1, signature="y") | ||
39 | |||
40 | #if type(arg) is gnsrecord.Data: | ||
41 | #return dbus.Struct([arg._recordtype, | ||
42 | |||
43 | if isinstance(arg, datetime.datetime): | ||
44 | if pretty: | ||
45 | return dbus.String(strings.absolute_time_to_string(arg), variant_level=1) | ||
46 | else: | ||
47 | return dbus.UInt64((arg - datetime.datetime(1970, 1, 1)).total_seconds() * 1000000, variant_level=1) | ||
48 | |||
49 | def handle_exception(e, daemon, daemon_address): | ||
50 | name = e.get_dbus_name() | ||
51 | message = e.get_dbus_message() | ||
52 | if not name.startswith("org.freedesktop.DBus.Error."): | ||
53 | raise e | ||
54 | name = name[len("org.freedesktop.DBus.Error."):] | ||
55 | |||
56 | if name == "Failed" or name == "InvalidArgs": | ||
57 | raise GNUNetDaemonError(message) | ||
58 | if name == "NoMemory": | ||
59 | raise MemoryError(message) | ||
60 | if name == "ServiceUnknown" or name == "NameHasNoOwner": | ||
61 | raise GNUNetDaemonError("Failed to contact " + daemon + " daemon at " + daemon_address) | ||
62 | if name == "NoReply" or name == "Timeout": | ||
63 | raise GNUNetDaemonError("Did not receive reply from " + daemon + " daemon at " + daemon_address + ". Daemon might of crashed") | ||
64 | raise e | ||
65 | |||
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 @@ | |||
1 | types = set([ | ||
2 | "any", | ||
3 | "fs_dblock", | ||
4 | "fs_iblock", | ||
5 | "fs_kblock", | ||
6 | "fs_sblock", | ||
7 | "fs_nblock", | ||
8 | "fs_ondemand", | ||
9 | "dht_hello", | ||
10 | "test", | ||
11 | "fs_ublock", | ||
12 | "dns", | ||
13 | "gns_namerecord", | ||
14 | "regex", | ||
15 | "regex_accept"]) | ||
16 | |||
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 @@ | |||
1 | from gnunet import _Key | ||
2 | import gnunet.strings as strings | ||
3 | |||
4 | class EcdsaPublicKey(_Key): | ||
5 | def __init__(self, arg): | ||
6 | _Key.__init__(self, arg, EcdsaPublicKey, 256) | ||
7 | |||
8 | def __repr__(self): | ||
9 | return "gnunet.crypto.EcdsaPublicKey('" + str(self) + "')" | ||
10 | |||
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 @@ | |||
1 | import dbus | ||
2 | |||
3 | import datetime | ||
4 | |||
5 | from gnunet import * | ||
6 | from gnunet._dbus_utils import * | ||
7 | |||
8 | import gnunet.block as block | ||
9 | |||
10 | get_requests = {} | ||
11 | requests_lock = threading.Lock() | ||
12 | |||
13 | class GetResult(threading.Thread): | ||
14 | def __init__(self, expiry, key, get_path, put_path, block_type, data, path): | ||
15 | threading.Thread.__init__(self) | ||
16 | self.expiry = expiry | ||
17 | self.key = key | ||
18 | self.get_path = get_path | ||
19 | self.put_path = put_path | ||
20 | self.block_type = block_type | ||
21 | self.data = data | ||
22 | self.path = path | ||
23 | self.daemon = True | ||
24 | self.start() | ||
25 | |||
26 | def run(self): | ||
27 | request = None | ||
28 | with requests_lock: | ||
29 | request = get_requests[self.path] | ||
30 | |||
31 | if request: | ||
32 | if request.record_route: | ||
33 | request.callback(self.block_type, self.key, self.data, self.expiry, get_path=self.get_path, put_path=self.put_path) | ||
34 | else: | ||
35 | request.callback(self.block_type, self.key, self.data, self.expiry) | ||
36 | |||
37 | def _result(expiry, key, get_path, put_path, block_type, data, path): | ||
38 | expiry = pythonize(expiry, datetime.datetime) | ||
39 | key = HashCode(key) | ||
40 | get_path = list(get_path) | ||
41 | put_path = list(put_path) | ||
42 | block_type = str(block_type) | ||
43 | data = bytearray(data) | ||
44 | GetResult(expiry, key, get_path, put_path, block_type, data, path) | ||
45 | |||
46 | sysbus.add_signal_receiver(_result, "result", "gnu.gnunet.dht.get", "gnu.gnunet.dht", path_keyword="path") | ||
47 | |||
48 | class GetRequest: | ||
49 | def __init__(self, path, callback, record_route): | ||
50 | self._path = path | ||
51 | self.callback = callback | ||
52 | self.record_route = record_route | ||
53 | |||
54 | def put(key, desired_replication_level, block_type, data, expiry=None, demultiplex_everywhere=False, record_route=False, bart=False): | ||
55 | key = dbusize(HashCode(key), True) | ||
56 | desired_replication_level = dbus.UInt32(desired_replication_level) | ||
57 | if block_type not in block.types: | ||
58 | raise ValueError("'block_type' must be one of %s" % block.types) | ||
59 | block_type = dbus.String(block_type, variant_level=1) | ||
60 | if expiry is not None: | ||
61 | if not isinstance(expiry, datetime.datetime): | ||
62 | raise TypeError("'expiry' must be a datetime.datetime") | ||
63 | expiry = dbusize(expiry) | ||
64 | else: | ||
65 | expiry = dbus.String("end of time", variant_level=1) | ||
66 | options = dbus.Array([], variant_level=1, signature="s") | ||
67 | if demultiplex_everywhere: | ||
68 | options += ["demultiplex_everywhere"] | ||
69 | if record_route: | ||
70 | options += ["record_route"] | ||
71 | if bart: | ||
72 | options += ["bart"] | ||
73 | data = dbus.Array(bytearray(data), signature="y") | ||
74 | |||
75 | try: | ||
76 | sysbus.get_object("gnu.gnunet.dht", "/").put(key, desired_replication_level, options, block_type, data, expiry) | ||
77 | except dbus.DBusException as e: | ||
78 | handle_exception(e, "dht", "gnu.gnunet.dht") | ||
79 | |||
80 | def get_start(callback, block_type, key, desired_replication_level, demultiplex_everywhere=False, record_route=False, bart=False): | ||
81 | if block_type not in block.types: | ||
82 | raise ValueError("'block_type' must be one of %s" % block.types) | ||
83 | block_type = dbus.String(block_type, variant_level=1) | ||
84 | key = dbusize(HashCode(key), True) | ||
85 | desired_replication_level = dbus.UInt32(desired_replication_level) | ||
86 | options = dbus.Array([], variant_level=1, signature="s") | ||
87 | if demultiplex_everywhere: | ||
88 | options += ["demultiplex_everywhere"] | ||
89 | if record_route: | ||
90 | options += ["record_route"] | ||
91 | if bart: | ||
92 | options += ["bart"] | ||
93 | |||
94 | ret = None | ||
95 | try: | ||
96 | with requests_lock: | ||
97 | path = sysbus.get_object("gnu.gnunet.dht", "/").get_start(block_type, key, desired_replication_level, options) | ||
98 | ret = GetRequest(path, callback, record_route) | ||
99 | get_requests[path] = ret | ||
100 | except dbus.DBusException as e: | ||
101 | handle_exception(e, "dht", "gnu.gnunet.dht") | ||
102 | |||
103 | return ret | ||
104 | |||
105 | |||
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 @@ | |||
1 | import dbus | ||
2 | |||
3 | from gnunet._dbus_utils import * | ||
4 | |||
5 | from gnunet import * | ||
6 | import gnunet.crypto as crypto | ||
7 | import gnunet.gnsrecord as gnsrecord | ||
8 | |||
9 | def lookup(name, zone, record_type, only_cached): | ||
10 | name = str(name) | ||
11 | zone = dbusize(crypto.EcdsaPublicKey(zone), True) | ||
12 | if record_type not in gnsrecord.types: | ||
13 | raise ValueError("'record_type' must be one of %s" % gnsrecord.types) | ||
14 | #record_type = dbus.UInt32(gnsrecord.types[record_type], variant_level=1) | ||
15 | record_type = dbus.String(record_type, variant_level=1) | ||
16 | only_cached = dbus.Boolean(only_cached) | ||
17 | |||
18 | try: | ||
19 | results = sysbus.get_object("gnu.gnunet.gns", "/").lookup(name, zone, record_type, only_cached) | ||
20 | except dbus.DBusException as e: | ||
21 | handle_exception(e, "gns", "gnu.gnunet.gns") | ||
22 | |||
23 | ret = [] | ||
24 | for r in results: | ||
25 | record_type = str(r[0]) | ||
26 | private = False | ||
27 | pending = False | ||
28 | shadow = False | ||
29 | relative = False | ||
30 | for f in r[1]: | ||
31 | if f == "private": | ||
32 | private = True | ||
33 | if f == "pending": | ||
34 | pending = True | ||
35 | if f == "shadow": | ||
36 | shadow = True | ||
37 | if f == "relative_expiration": | ||
38 | relative = True | ||
39 | data = str(r[2]) | ||
40 | expiration_time = None | ||
41 | if relative: | ||
42 | expiration_time = pythonize(r[3], datetime.timedelta) | ||
43 | else: | ||
44 | expiration_time = pythonize(r[3], datetime.datetime) | ||
45 | ret.append(gnsrecord.Data(record_type, data, expiration_time, private, pending, shadow)) | ||
46 | |||
47 | return ret | ||
48 | |||
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 @@ | |||
1 | import datetime | ||
2 | |||
3 | dns_types = { | ||
4 | "A": 1, | ||
5 | "NS": 2, | ||
6 | "CNAME": 5, | ||
7 | "SOA": 6, | ||
8 | "PTR": 12, | ||
9 | "MX": 15, | ||
10 | "TXT": 16, | ||
11 | "AAAA": 28, | ||
12 | "TLSA": 52} | ||
13 | |||
14 | gns_types = { | ||
15 | "PKEY": 65536, | ||
16 | "NICK": 65537, | ||
17 | "LEHO": 65538, | ||
18 | "VPN": 65539, | ||
19 | "GNS2DNS": 65540} | ||
20 | |||
21 | types = dict(list(dns_types.items()) + list(gns_types.items())) | ||
22 | |||
23 | class Data: | ||
24 | def __init__(self, record_type, data, expiration_time=None, private=None, pending=None, shadow=None): | ||
25 | self.record_type = str(record_type) | ||
26 | if record_type not in types: | ||
27 | raise ValueError("'record_type' must be one of %s" % types) | ||
28 | #self.data = bytearray(data) | ||
29 | self.data = str(data) | ||
30 | if expiration_time is not None and not isinstance(expiration_time, datetime.datetime) or isinstance(expiration_time, datetime.timedelta): | ||
31 | raise TypeError("'expiration_time' must be a datetime.datetime or a datetime.timedelta") | ||
32 | self.expiration_time = expiration_time | ||
33 | self.private = private | ||
34 | self.pending = pending | ||
35 | self.shadow = shadow | ||
36 | |||
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 @@ | |||
1 | import datetime | ||
2 | |||
3 | from gnunet import * | ||
4 | |||
5 | encTable = "0123456789ABCDEFGHIJKLMNOPQRSTUV" | ||
6 | |||
7 | def data_to_string(data): | ||
8 | data = bytearray(data) | ||
9 | size = len(data) | ||
10 | bits = 0 | ||
11 | rpos = 0 | ||
12 | vbit = 0 | ||
13 | ret = "" | ||
14 | while rpos < size: | ||
15 | while rpos < size and vbit < 5: | ||
16 | bits = (bits << 8) | data[rpos] | ||
17 | rpos += 1 | ||
18 | vbit += 8 | ||
19 | while vbit >= 5: | ||
20 | vbit -= 5 | ||
21 | ret += encTable[(bits >> vbit) & 31] | ||
22 | if vbit > 0: | ||
23 | ret += encTable[(bits << (5 - vbit)) & 31] | ||
24 | return ret | ||
25 | |||
26 | def string_to_data(s): | ||
27 | s = str(s) | ||
28 | size = len(s) | ||
29 | bits = 0 | ||
30 | rpos = 0 | ||
31 | vbit = 0 | ||
32 | ret = bytearray([]) | ||
33 | try: | ||
34 | while rpos < size: | ||
35 | while rpos < size and vbit < 8: | ||
36 | bits = (bits << 5) | int(s[rpos], 32) | ||
37 | rpos += 1 | ||
38 | vbit += 5 | ||
39 | while vbit >= 8: | ||
40 | vbit -= 8 | ||
41 | ret.append((bits >> vbit) & 255) | ||
42 | if vbit > 0: | ||
43 | if bits & ((1 << vbit) - 1) != 0: | ||
44 | raise ValueError("") | ||
45 | except ValueError: | ||
46 | raise ValueError("'" + s + "' is not a valid data-encoding string") | ||
47 | return ret | ||
48 | |||
49 | def absolute_time_to_string(t): | ||
50 | return t.strftime("%a %b %d %H:%M:%S %Y") | ||
51 | |||
52 | def string_to_absolute_time(s): | ||
53 | if s == "end of time": | ||
54 | return None | ||
55 | try: | ||
56 | return datetime.datetime.strptime(s, "%a %b %d %H:%M:%S %Y") | ||
57 | except ValueError: | ||
58 | pass | ||
59 | try: | ||
60 | return datetime.datetime.strptime(s, "%c") | ||
61 | except ValueError: | ||
62 | pass | ||
63 | try: | ||
64 | return datetime.datetime.strptime(s, "%Ec") | ||
65 | except ValueError: | ||
66 | pass | ||
67 | try: | ||
68 | return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S") | ||
69 | except ValueError: | ||
70 | pass | ||
71 | try: | ||
72 | return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M") | ||
73 | except ValueError: | ||
74 | pass | ||
75 | try: | ||
76 | return datetime.datetime.strptime(s, "%x") | ||
77 | except ValueError: | ||
78 | pass | ||
79 | try: | ||
80 | return datetime.datetime.strptime(s, "%Ex") | ||
81 | except ValueError: | ||
82 | pass | ||
83 | try: | ||
84 | return datetime.datetime.strptime(s, "%Y-%m-%d") | ||
85 | except ValueError: | ||
86 | pass | ||
87 | try: | ||
88 | return datetime.datetime.strptime(s, "%Y-%m") | ||
89 | except ValueError: | ||
90 | pass | ||
91 | try: | ||
92 | return datetime.datetime.strptime(s, "%Y") | ||
93 | except ValueError: | ||
94 | pass | ||
95 | raise ValueError("%s is not a properly formatted time string" % s) | ||
96 | |||
97 | |||