kadepy 0.2.1__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kadepy/__init__.py +26 -0
- kadepy/_core.cp314-win_amd64.pyd +0 -0
- kadepy/_hyperswarm.cp314-win_amd64.pyd +0 -0
- kadepy/ipc.py +136 -0
- kadepy/libsodium.dll +0 -0
- kadepy/native_hyperswarm.py +50 -0
- kadepy/swarm.py +261 -0
- kadepy-0.2.1.dist-info/METADATA +133 -0
- kadepy-0.2.1.dist-info/RECORD +12 -0
- kadepy-0.2.1.dist-info/WHEEL +5 -0
- kadepy-0.2.1.dist-info/licenses/LICENSE +21 -0
- kadepy-0.2.1.dist-info/top_level.txt +1 -0
kadepy/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from ._core import (
|
|
2
|
+
create_swarm,
|
|
3
|
+
xor_distance,
|
|
4
|
+
set_callback,
|
|
5
|
+
dht_ping,
|
|
6
|
+
dht_find_node,
|
|
7
|
+
dht_announce_peer,
|
|
8
|
+
dht_get_peers,
|
|
9
|
+
dump_routing_table
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .swarm import Swarm
|
|
13
|
+
|
|
14
|
+
__version__ = "0.2.1"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'create_swarm',
|
|
18
|
+
'xor_distance',
|
|
19
|
+
'set_callback',
|
|
20
|
+
'dht_ping',
|
|
21
|
+
'dht_find_node',
|
|
22
|
+
'dht_announce_peer',
|
|
23
|
+
'dht_get_peers',
|
|
24
|
+
'dump_routing_table',
|
|
25
|
+
'Swarm'
|
|
26
|
+
]
|
|
Binary file
|
|
Binary file
|
kadepy/ipc.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
from kadepy.swarm import Swarm
|
|
6
|
+
|
|
7
|
+
# Configure logging to stderr to avoid polluting stdout (used for IPC)
|
|
8
|
+
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
|
|
9
|
+
logger = logging.getLogger("KadePy-IPC")
|
|
10
|
+
|
|
11
|
+
class IPCBridge:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.swarm = Swarm()
|
|
14
|
+
self.running = True
|
|
15
|
+
|
|
16
|
+
# Override the swarm callback to send events to stdout
|
|
17
|
+
self.swarm.set_callback(self.on_dht_event)
|
|
18
|
+
|
|
19
|
+
def on_dht_event(self, event_type, data):
|
|
20
|
+
"""Callback from DHT C-extension, forward to IPC stdout"""
|
|
21
|
+
message = {
|
|
22
|
+
"jsonrpc": "2.0",
|
|
23
|
+
"method": "dht_event",
|
|
24
|
+
"params": {
|
|
25
|
+
"type": event_type,
|
|
26
|
+
"data": data
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
self.send_response(message)
|
|
30
|
+
|
|
31
|
+
def send_response(self, msg):
|
|
32
|
+
"""Send JSON line to stdout"""
|
|
33
|
+
try:
|
|
34
|
+
sys.stdout.write(json.dumps(msg) + "\n")
|
|
35
|
+
sys.stdout.flush()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Failed to write to stdout: {e}")
|
|
38
|
+
|
|
39
|
+
def handle_request(self, req):
|
|
40
|
+
if "method" not in req:
|
|
41
|
+
return {"error": "Missing method"}
|
|
42
|
+
|
|
43
|
+
method = req["method"]
|
|
44
|
+
params = req.get("params", {})
|
|
45
|
+
msg_id = req.get("id", None)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = None
|
|
49
|
+
if method == "ping":
|
|
50
|
+
# params: ip, port
|
|
51
|
+
# Swarm.bootstrap calls dht_ping under the hood, but Swarm has no direct ping method exposed
|
|
52
|
+
# except via bootstrap which adds to known nodes.
|
|
53
|
+
# Or we can use dht_ping directly if we import it, but better to use Swarm methods.
|
|
54
|
+
# Let's add a ping helper to Swarm or use bootstrap.
|
|
55
|
+
self.swarm.bootstrap(params["ip"], params["port"])
|
|
56
|
+
result = "Ping sent"
|
|
57
|
+
elif method == "find_node":
|
|
58
|
+
# params: ip, port, target_id (hex)
|
|
59
|
+
# Swarm has find_node_iterative, but that's a high-level op.
|
|
60
|
+
# If we want low-level find_node packet, Swarm doesn't expose it directly as method.
|
|
61
|
+
# But we can import dht_find_node from _core.
|
|
62
|
+
# However, the user probably wants the iterative lookup?
|
|
63
|
+
# "find_node" in RPC usually implies sending a FIND_NODE packet.
|
|
64
|
+
# But "find_node_iterative" is more useful.
|
|
65
|
+
# Let's map "find_node" to the iterative lookup if target_id is provided,
|
|
66
|
+
# OR if ip/port provided, send a single packet.
|
|
67
|
+
# The node_client.js sends ip/port/target_id, so it looks like a single packet request.
|
|
68
|
+
|
|
69
|
+
target = bytes.fromhex(params["target_id"])
|
|
70
|
+
|
|
71
|
+
# We need to access the low-level functions or expose them in Swarm
|
|
72
|
+
from ._core import dht_find_node
|
|
73
|
+
dht_find_node(params["ip"], params["port"], target)
|
|
74
|
+
|
|
75
|
+
result = "FindNode sent"
|
|
76
|
+
elif method == "announce":
|
|
77
|
+
# params: ip, port, info_hash (hex), announced_port
|
|
78
|
+
info_hash = bytes.fromhex(params["info_hash"])
|
|
79
|
+
from ._core import dht_announce_peer
|
|
80
|
+
dht_announce_peer(params["ip"], params["port"], info_hash, params["announced_port"])
|
|
81
|
+
result = "Announce sent"
|
|
82
|
+
elif method == "get_peers":
|
|
83
|
+
# params: ip, port, info_hash (hex)
|
|
84
|
+
info_hash = bytes.fromhex(params["info_hash"])
|
|
85
|
+
from ._core import dht_get_peers
|
|
86
|
+
dht_get_peers(params["ip"], params["port"], info_hash)
|
|
87
|
+
result = "GetPeers sent"
|
|
88
|
+
elif method == "start":
|
|
89
|
+
# params: port (optional)
|
|
90
|
+
port = params.get("port", 8000)
|
|
91
|
+
# In a real app we might start a listener here if Swarm wasn't already started
|
|
92
|
+
# But Swarm starts in __init__ currently.
|
|
93
|
+
# We could re-bind or just return info.
|
|
94
|
+
|
|
95
|
+
# Swarm doesn't store local ID in python object, it's in C.
|
|
96
|
+
# We can't easily get it without C extension modification or reading stdout logs.
|
|
97
|
+
# For now, return a placeholder ID.
|
|
98
|
+
result = {"status": "running", "local_id": "00"*32}
|
|
99
|
+
else:
|
|
100
|
+
return {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": msg_id}
|
|
101
|
+
|
|
102
|
+
return {"jsonrpc": "2.0", "result": result, "id": msg_id}
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.exception("Error handling request")
|
|
106
|
+
return {"jsonrpc": "2.0", "error": {"code": -32000, "message": str(e)}, "id": msg_id}
|
|
107
|
+
|
|
108
|
+
def run(self):
|
|
109
|
+
logger.info("IPC Bridge started. Waiting for JSON-RPC on stdin...")
|
|
110
|
+
while self.running:
|
|
111
|
+
try:
|
|
112
|
+
line = sys.stdin.readline()
|
|
113
|
+
if not line:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
line = line.strip()
|
|
117
|
+
if not line:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
req = json.loads(line)
|
|
122
|
+
resp = self.handle_request(req)
|
|
123
|
+
if resp:
|
|
124
|
+
self.send_response(resp)
|
|
125
|
+
except json.JSONDecodeError:
|
|
126
|
+
self.send_response({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None})
|
|
127
|
+
|
|
128
|
+
except KeyboardInterrupt:
|
|
129
|
+
break
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"IPC Loop Error: {e}")
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
bridge = IPCBridge()
|
|
136
|
+
bridge.run()
|
kadepy/libsodium.dll
ADDED
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from ._hyperswarm import HyperswarmNode as NativeNode
|
|
2
|
+
|
|
3
|
+
class NativeHyperswarm:
|
|
4
|
+
"""
|
|
5
|
+
Python wrapper for the native C Hyperswarm extension.
|
|
6
|
+
This eliminates the need for the Node.js bridge.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._node = NativeNode()
|
|
10
|
+
|
|
11
|
+
def join(self, topic):
|
|
12
|
+
"""
|
|
13
|
+
Join a topic on the Hyperswarm network.
|
|
14
|
+
Topic can be bytes or hex string.
|
|
15
|
+
"""
|
|
16
|
+
if isinstance(topic, bytes):
|
|
17
|
+
topic_hex = topic.hex()
|
|
18
|
+
else:
|
|
19
|
+
topic_hex = topic
|
|
20
|
+
self._node.join(topic_hex)
|
|
21
|
+
|
|
22
|
+
def leave(self, topic):
|
|
23
|
+
"""Leave a topic."""
|
|
24
|
+
if isinstance(topic, bytes):
|
|
25
|
+
topic_hex = topic.hex()
|
|
26
|
+
else:
|
|
27
|
+
topic_hex = topic
|
|
28
|
+
self._node.leave(topic_hex)
|
|
29
|
+
|
|
30
|
+
def poll(self):
|
|
31
|
+
"""Poll for events."""
|
|
32
|
+
return self._node.poll()
|
|
33
|
+
|
|
34
|
+
def get_port(self):
|
|
35
|
+
"""Get the bound local port."""
|
|
36
|
+
return self._node.get_port()
|
|
37
|
+
|
|
38
|
+
def send_debug(self, ip, port, msg):
|
|
39
|
+
"""Send a debug packet."""
|
|
40
|
+
return self._node.send_debug(ip, port, msg)
|
|
41
|
+
|
|
42
|
+
def add_peer(self, ip, port, id_hex=None):
|
|
43
|
+
"""Manually add a peer to the routing table."""
|
|
44
|
+
if hasattr(self._node, 'add_peer'):
|
|
45
|
+
if id_hex:
|
|
46
|
+
return self._node.add_peer(ip, port, id_hex)
|
|
47
|
+
else:
|
|
48
|
+
return self._node.add_peer(ip, port)
|
|
49
|
+
else:
|
|
50
|
+
print("Warning: Native extension does not support add_peer yet.")
|
kadepy/swarm.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import struct
|
|
3
|
+
import time
|
|
4
|
+
import threading
|
|
5
|
+
import json
|
|
6
|
+
from ._core import (
|
|
7
|
+
create_swarm,
|
|
8
|
+
set_callback,
|
|
9
|
+
dht_ping,
|
|
10
|
+
dht_find_node,
|
|
11
|
+
dht_announce_peer,
|
|
12
|
+
dht_get_peers,
|
|
13
|
+
dump_routing_table,
|
|
14
|
+
xor_distance
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Constants from protocol.h
|
|
18
|
+
MSG_PING = 0
|
|
19
|
+
MSG_PONG = 1
|
|
20
|
+
MSG_FIND_NODE = 2
|
|
21
|
+
MSG_FOUND_NODES = 3
|
|
22
|
+
MSG_ANNOUNCE_PEER = 4
|
|
23
|
+
MSG_GET_PEERS = 5
|
|
24
|
+
MSG_PEERS = 6
|
|
25
|
+
|
|
26
|
+
class Swarm:
|
|
27
|
+
def __init__(self, port=0):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the Swarm node.
|
|
30
|
+
:param port: Port to bind to (0 for random).
|
|
31
|
+
"""
|
|
32
|
+
# create_swarm returns the bound port
|
|
33
|
+
self.port = create_swarm(port)
|
|
34
|
+
self.peers = {} # info_hash -> list of peers
|
|
35
|
+
self._callback = None
|
|
36
|
+
|
|
37
|
+
self.known_nodes = {} # (ip, port) -> last_seen_timestamp
|
|
38
|
+
self.lock = threading.Lock()
|
|
39
|
+
|
|
40
|
+
# Hyperswarm Bridge
|
|
41
|
+
self._event_callbacks = {}
|
|
42
|
+
self._bridge_peer = None
|
|
43
|
+
|
|
44
|
+
# Register global callback
|
|
45
|
+
set_callback(self._on_packet)
|
|
46
|
+
|
|
47
|
+
def _on_packet(self, sender_id, msg_type, ip, port, payload, signature=None):
|
|
48
|
+
"""
|
|
49
|
+
Internal callback from C extension.
|
|
50
|
+
"""
|
|
51
|
+
# Signature Verification
|
|
52
|
+
# For now, we just print if it's missing (shouldn't be with new C code)
|
|
53
|
+
# In a real implementation, we would use:
|
|
54
|
+
# try:
|
|
55
|
+
# verify_key = nacl.signing.VerifyKey(sender_id)
|
|
56
|
+
# verify_key.verify(msg_content, signature)
|
|
57
|
+
# except:
|
|
58
|
+
# return # Drop packet
|
|
59
|
+
|
|
60
|
+
# IP is already a string from C extension
|
|
61
|
+
ip_str = ip
|
|
62
|
+
|
|
63
|
+
# Update known nodes
|
|
64
|
+
with self.lock:
|
|
65
|
+
self.known_nodes[(ip_str, port)] = time.time()
|
|
66
|
+
|
|
67
|
+
if msg_type == MSG_FOUND_NODES:
|
|
68
|
+
# Payload is a list of dicts {'id': bytes, 'ip': str, 'port': int}
|
|
69
|
+
if isinstance(payload, list):
|
|
70
|
+
with self.lock:
|
|
71
|
+
for node in payload:
|
|
72
|
+
self.known_nodes[(node['ip'], node['port'])] = time.time()
|
|
73
|
+
|
|
74
|
+
# print(f"[Swarm] Packet {msg_type} from {ip_str}:{port}")
|
|
75
|
+
|
|
76
|
+
if self._callback:
|
|
77
|
+
# Backwards compatibility if user callback expects 5 args
|
|
78
|
+
try:
|
|
79
|
+
self._callback(msg_type, payload) # Simplified for IPC
|
|
80
|
+
except TypeError:
|
|
81
|
+
self._callback(msg_type, payload) # Wait, IPC callback expects (type, data)
|
|
82
|
+
|
|
83
|
+
def set_callback(self, callback):
|
|
84
|
+
"""
|
|
85
|
+
Set a callback for incoming packets.
|
|
86
|
+
Callback signature: (sender_id: bytes, msg_type: int, ip: str, port: int, payload: any)
|
|
87
|
+
"""
|
|
88
|
+
self._callback = callback
|
|
89
|
+
|
|
90
|
+
def bootstrap(self, ip, port):
|
|
91
|
+
"""
|
|
92
|
+
Bootstrap the node by contacting a known node.
|
|
93
|
+
"""
|
|
94
|
+
dht_ping(ip, port)
|
|
95
|
+
# We also speculatively add it to known nodes
|
|
96
|
+
with self.lock:
|
|
97
|
+
self.known_nodes[(ip, port)] = time.time()
|
|
98
|
+
|
|
99
|
+
def _get_closest_nodes(self, target_id, k=8):
|
|
100
|
+
"""
|
|
101
|
+
Get k closest known nodes to target_id.
|
|
102
|
+
Since we don't have the node IDs for all known_nodes (only IPs),
|
|
103
|
+
we rely on the C routing table for the "best" nodes, but we can't query it.
|
|
104
|
+
|
|
105
|
+
Workaround: We just return all known nodes because we are lazy
|
|
106
|
+
and maintaining IDs for all nodes in Python requires parsing all packets.
|
|
107
|
+
|
|
108
|
+
Ideally, we should store ID in known_nodes too.
|
|
109
|
+
"""
|
|
110
|
+
# For now, return all known nodes up to k (random/recent)
|
|
111
|
+
with self.lock:
|
|
112
|
+
nodes = list(self.known_nodes.keys())
|
|
113
|
+
return nodes[:k]
|
|
114
|
+
|
|
115
|
+
def find_node_iterative(self, target_id, timeout=2.0):
|
|
116
|
+
"""
|
|
117
|
+
Perform an iterative lookup for target_id.
|
|
118
|
+
Returns a list of (ip, port) of closest nodes found.
|
|
119
|
+
"""
|
|
120
|
+
# Start by asking our known nodes
|
|
121
|
+
start_time = time.time()
|
|
122
|
+
queried = set()
|
|
123
|
+
|
|
124
|
+
# Simple implementation: ask everyone we know, collecting more nodes
|
|
125
|
+
# In a real implementation, we would sort by XOR distance.
|
|
126
|
+
|
|
127
|
+
while time.time() - start_time < timeout:
|
|
128
|
+
candidates = self._get_closest_nodes(target_id, k=20)
|
|
129
|
+
to_query = [n for n in candidates if n not in queried]
|
|
130
|
+
|
|
131
|
+
if not to_query:
|
|
132
|
+
time.sleep(0.1)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
for ip, port in to_query:
|
|
136
|
+
dht_find_node(ip, port, target_id)
|
|
137
|
+
queried.add((ip, port))
|
|
138
|
+
|
|
139
|
+
# Wait a bit for responses to populate known_nodes
|
|
140
|
+
time.sleep(0.5)
|
|
141
|
+
|
|
142
|
+
return self._get_closest_nodes(target_id)
|
|
143
|
+
|
|
144
|
+
def announce(self, topic_hash, port=None):
|
|
145
|
+
"""
|
|
146
|
+
Announce this node for a specific topic.
|
|
147
|
+
:param topic_hash: 32-byte bytes object.
|
|
148
|
+
:param port: Port to announce (defaults to bound port).
|
|
149
|
+
"""
|
|
150
|
+
if port is None:
|
|
151
|
+
port = self.port
|
|
152
|
+
|
|
153
|
+
# 1. Find closest nodes to topic_hash
|
|
154
|
+
closest_nodes = self.find_node_iterative(topic_hash)
|
|
155
|
+
|
|
156
|
+
# 2. Send announce_peer to them
|
|
157
|
+
for ip, target_port in closest_nodes:
|
|
158
|
+
dht_announce_peer(ip, target_port, topic_hash, port)
|
|
159
|
+
|
|
160
|
+
def get_peers(self, topic_hash, timeout=2.0):
|
|
161
|
+
"""
|
|
162
|
+
Find peers for a topic.
|
|
163
|
+
"""
|
|
164
|
+
# 1. Find closest nodes (they might store the peers)
|
|
165
|
+
closest_nodes = self.find_node_iterative(topic_hash, timeout=timeout/2)
|
|
166
|
+
|
|
167
|
+
# 2. Ask them for peers
|
|
168
|
+
for ip, port in closest_nodes:
|
|
169
|
+
dht_get_peers(ip, port, topic_hash)
|
|
170
|
+
|
|
171
|
+
# 3. Wait for MSG_PEERS in callback (handled by user callback)
|
|
172
|
+
# Here we just trigger the requests.
|
|
173
|
+
|
|
174
|
+
def announce_to(self, target_ip, target_port, topic_hash, port=None):
|
|
175
|
+
"""
|
|
176
|
+
Directly announce to a specific node.
|
|
177
|
+
"""
|
|
178
|
+
if port is None:
|
|
179
|
+
port = self.port
|
|
180
|
+
dht_announce_peer(target_ip, target_port, topic_hash, port)
|
|
181
|
+
|
|
182
|
+
def lookup_from(self, target_ip, target_port, topic_hash):
|
|
183
|
+
"""
|
|
184
|
+
Directly ask a node for peers.
|
|
185
|
+
"""
|
|
186
|
+
dht_get_peers(target_ip, target_port, topic_hash)
|
|
187
|
+
|
|
188
|
+
def ping(self, ip, port):
|
|
189
|
+
dht_ping(ip, port)
|
|
190
|
+
|
|
191
|
+
def dump_table(self):
|
|
192
|
+
dump_routing_table()
|
|
193
|
+
|
|
194
|
+
# -------------------------------------------------------------------------
|
|
195
|
+
# Hyperswarm Bridge Methods
|
|
196
|
+
# -------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
def on(self, event, callback):
|
|
199
|
+
"""Register a callback for bridge events (e.g. 'data')."""
|
|
200
|
+
self._event_callbacks[event] = callback
|
|
201
|
+
|
|
202
|
+
def join(self, topic):
|
|
203
|
+
"""
|
|
204
|
+
Join a topic on the Hyperswarm network via the bridge.
|
|
205
|
+
Topic should be a 32-byte hex string or bytes.
|
|
206
|
+
"""
|
|
207
|
+
if not self._bridge_peer:
|
|
208
|
+
self._bridge_peer = Peer()
|
|
209
|
+
try:
|
|
210
|
+
self._bridge_peer.connect()
|
|
211
|
+
except ConnectionRefusedError:
|
|
212
|
+
print("[Swarm] Could not connect to Hyperswarm Bridge on port 5000.")
|
|
213
|
+
print("[Swarm] Please run 'node bridge/hyperswarm_bridge.js' first.")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
if isinstance(topic, bytes):
|
|
217
|
+
topic_hex = topic.hex()
|
|
218
|
+
else:
|
|
219
|
+
topic_hex = topic
|
|
220
|
+
|
|
221
|
+
msg = {"op": "join", "topic": topic_hex}
|
|
222
|
+
self._bridge_peer.write(msg)
|
|
223
|
+
|
|
224
|
+
def run(self):
|
|
225
|
+
"""
|
|
226
|
+
Start the event loop to listen for bridge messages.
|
|
227
|
+
Blocking call.
|
|
228
|
+
"""
|
|
229
|
+
if not self._bridge_peer:
|
|
230
|
+
print("[Swarm] Bridge not connected.")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
buffer = ""
|
|
234
|
+
while True:
|
|
235
|
+
try:
|
|
236
|
+
data = self._bridge_peer.socket.recv(4096)
|
|
237
|
+
if not data:
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
buffer += data.decode("utf-8")
|
|
241
|
+
|
|
242
|
+
while "\n" in buffer:
|
|
243
|
+
line, buffer = buffer.split("\n", 1)
|
|
244
|
+
if not line.strip():
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
msg = json.loads(line)
|
|
249
|
+
self._on_bridge_event(msg)
|
|
250
|
+
except json.JSONDecodeError:
|
|
251
|
+
print(f"[Swarm] Invalid JSON from bridge: {line}")
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
print(f"[Swarm] Error in run loop: {e}")
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
def _on_bridge_event(self, msg):
|
|
258
|
+
event = msg.get("event")
|
|
259
|
+
if event == "data" and "data" in self._event_callbacks:
|
|
260
|
+
self._event_callbacks["data"](msg.get("data"))
|
|
261
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kadepy
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: High-performance P2P Swarm implementation in C/Python with Signed RPC and IPC
|
|
5
|
+
Home-page: https://github.com/ON00dev/KadePy
|
|
6
|
+
Author: KadePy Contributors
|
|
7
|
+
Author-email: KadePy Contributors <maintainer@example.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/ON00dev/KadePy
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/ON00dev/KadePy/issues
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: C
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
22
|
+
Classifier: Operating System :: POSIX
|
|
23
|
+
Classifier: Operating System :: MacOS
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
|
|
32
|
+
# KadePy
|
|
33
|
+
|
|
34
|
+
**High-Performance Kademlia DHT Implementation for Python**
|
|
35
|
+
|
|
36
|
+
KadePy is a robust and efficient Distributed Hash Table (DHT) library implementing the Kademlia protocol. It combines a high-performance C extension core for network handling and routing with a user-friendly Python wrapper.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **High Performance**: Core logic (UDP reactor, routing table, storage, protocol) implemented in C for minimal overhead and maximum throughput.
|
|
41
|
+
- **Cross-Platform**: Fully compatible with Windows, Linux, and macOS.
|
|
42
|
+
- **Hyperswarm Native Extension (New in v0.2.0)**:
|
|
43
|
+
- **Noise Handshake (XX Pattern)**: Secure, authenticated connections using `libsodium` (Ed25519/Curve25519).
|
|
44
|
+
- **UDX Transport**: Reliable, encrypted UDP transport with congestion control and packet ordering.
|
|
45
|
+
- **Holepunching**: Built-in NAT traversal.
|
|
46
|
+
- **Secure**:
|
|
47
|
+
- Uses **XSalsa20-Poly1305** for transport encryption.
|
|
48
|
+
- Implements secure random number generation.
|
|
49
|
+
- **Concurrency**: Thread-safe architecture with a dedicated network reactor thread, releasing the GIL whenever possible.
|
|
50
|
+
- **Easy to Use**: Simple Python API for creating nodes, storing values, and finding peers.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
### From Source
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/ON00dev/KadePy.git
|
|
58
|
+
cd KadePy
|
|
59
|
+
pip install .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Requirements
|
|
63
|
+
|
|
64
|
+
- Python 3.10+
|
|
65
|
+
- C Compiler (GCC/Clang on Linux/macOS, MSVC on Windows)
|
|
66
|
+
- Libsodium (automatically bundled on Windows if built via setup.py)
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
### Basic Node
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from kadepy import Swarm
|
|
74
|
+
import time
|
|
75
|
+
|
|
76
|
+
# Create a node on a random port
|
|
77
|
+
node = Swarm()
|
|
78
|
+
print(f"Node started on port {node.port}")
|
|
79
|
+
|
|
80
|
+
# Create another node to bootstrap
|
|
81
|
+
bootstrap_node = Swarm(port=8000)
|
|
82
|
+
print("Bootstrap node on port 8000")
|
|
83
|
+
|
|
84
|
+
# Bootstrap the first node
|
|
85
|
+
node.bootstrap("127.0.0.1", 8000)
|
|
86
|
+
|
|
87
|
+
# Announce a topic (hash)
|
|
88
|
+
topic_hash = b'\x00' * 32 # 32-byte hash
|
|
89
|
+
node.announce(topic_hash, node.port)
|
|
90
|
+
|
|
91
|
+
# Find peers for the topic
|
|
92
|
+
peers = node.get_peers(topic_hash)
|
|
93
|
+
print("Found peers:", peers)
|
|
94
|
+
|
|
95
|
+
# Keep alive
|
|
96
|
+
try:
|
|
97
|
+
while True:
|
|
98
|
+
time.sleep(1)
|
|
99
|
+
except KeyboardInterrupt:
|
|
100
|
+
print("Stopping...")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Architecture
|
|
104
|
+
|
|
105
|
+
KadePy follows a hybrid architecture:
|
|
106
|
+
|
|
107
|
+
1. **C Extension (`_core`)**:
|
|
108
|
+
* **UDP Reactor**: Handles non-blocking socket I/O using `select` (or platform specific polling).
|
|
109
|
+
* **Protocol**: Implements Kademlia message serialization/deserialization.
|
|
110
|
+
* **Routing**: Manages K-Buckets and node lookups.
|
|
111
|
+
* **Storage**: In-memory key-value storage with expiration.
|
|
112
|
+
* **Crypto**: ChaCha20 encryption and secure RNG.
|
|
113
|
+
* **Hyperswarm (Experimental)**: Native C implementation of Noise/UDX (work in progress).
|
|
114
|
+
|
|
115
|
+
2. **Python Wrapper (`Swarm`)**:
|
|
116
|
+
* Provides a high-level API.
|
|
117
|
+
* Manages the C reactor lifecycle.
|
|
118
|
+
* Handles bootstrapping and iterative lookups.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
Contributions are welcome! Please check out the [CONTRIBUTING.md](CONTRIBUTING.md) guide for details.
|
|
124
|
+
|
|
125
|
+
1. Fork the project.
|
|
126
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
|
|
127
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
|
|
128
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`).
|
|
129
|
+
5. Open a Pull Request.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
kadepy/__init__.py,sha256=QoR-KzlLPfbbCkJ2bXLmm0iffATn4jJK-6MjTPn4mkU,420
|
|
2
|
+
kadepy/_core.cp314-win_amd64.pyd,sha256=OfhGTtt37msEWseTK3H7LSgWNm7O4TB4tlr8c8dfaR8,26112
|
|
3
|
+
kadepy/_hyperswarm.cp314-win_amd64.pyd,sha256=q_HiZeb4LaYabbRvfmtSHf2lOIqWJUD5QvKcEyIS0HU,26624
|
|
4
|
+
kadepy/ipc.py,sha256=iInSh5QQOg-uze3c7_4n2TsFaidgLMnPDmbnU5rsrjE,5849
|
|
5
|
+
kadepy/libsodium.dll,sha256=c0AkA79G7Ghl0ql24UYnqXgz1zqPiuSDwO7xXTHBOeo,424448
|
|
6
|
+
kadepy/native_hyperswarm.py,sha256=O4fXnC6e5TeFIvVzJBwbYtEk_t3WUxSXrp-D4Dr0mjA,1504
|
|
7
|
+
kadepy/swarm.py,sha256=a1nlj_kh793GSCs5i5_hNNXHH6VuKcgTqJxBDvudn7w,8665
|
|
8
|
+
kadepy-0.2.1.dist-info/licenses/LICENSE,sha256=POtz6ePuuspuzC3-AOg8Xj7zUiy_aX6QLMMqM7ToTJg,1097
|
|
9
|
+
kadepy-0.2.1.dist-info/METADATA,sha256=fntxlT6Ak9T9reDBKEU2LWt5i_z3XUtDAFGgffMdOWU,4578
|
|
10
|
+
kadepy-0.2.1.dist-info/WHEEL,sha256=wqPbqRzxuMU6tx88euOi7_6Ralk4Ot5iiL-wT6LzqUA,102
|
|
11
|
+
kadepy-0.2.1.dist-info/top_level.txt,sha256=EsK8Hd8zV9G0s5HfeUQru8TxNUSogdrnTw9yqPYhu6Y,7
|
|
12
|
+
kadepy-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KadePy Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kadepy
|