From f9272cbac9819f8c566c3c0810d485d536794c0b Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 19 Aug 2023 02:52:40 +0300 Subject: [PATCH 01/12] Define a base class for connection to subclass for special purposes --- minode/connection.py | 168 ++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 80 deletions(-) diff --git a/minode/connection.py b/minode/connection.py index c74af5d..b7f9a75 100644 --- a/minode/connection.py +++ b/minode/connection.py @@ -15,8 +15,11 @@ import time from . import message, shared, structure -class Connection(threading.Thread): - """The connection object""" +class ConnectionBase(threading.Thread): + """ + Common code for the connection thread + with minimum command handlers to reuse + """ def __init__( self, host, port, s=None, network='ip', server=False, i2p_remote_dest=b'' @@ -344,89 +347,13 @@ class Connection(threading.Thread): break def _process_message(self, m): - if m.command == b'version': - version = message.Version.from_message(m) - if shared.stream not in version.streams: - raise ValueError('message not for stream %i' % shared.stream) - logging.debug('%s:%s -> %s', self.host_print, self.port, version) - if ( - version.protocol_version != shared.protocol_version - or version.nonce == shared.nonce - ): - self.status = 'disconnecting' - self.send_queue.put(None) - else: - logging.info( - '%s:%s claims to be %s', - self.host_print, self.port, version.user_agent) - self.send_queue.put(message.Message(b'verack', b'')) - self.verack_sent = True - self.remote_version = version - if not self.server: - self.send_queue.put('fully_established') - if self.network == 'ip': - shared.address_advertise_queue.put(structure.NetAddr( - version.services, self.host, self.port)) - shared.node_pool.add((self.host, self.port)) - elif self.network == 'i2p': - shared.i2p_node_pool.add((self.host, 'i2p')) - if self.network == 'ip': - shared.address_advertise_queue.put(structure.NetAddr( - shared.services, version.host, shared.listening_port)) - if self.server: - if self.network == 'ip': - self.send_queue.put( - message.Version(self.host, self.port)) - else: - self.send_queue.put(message.Version('127.0.0.1', 7656)) - - elif m.command == b'verack': + if m.command == b'verack': self.verack_received = True logging.debug( '%s:%s -> %s', self.host_print, self.port, 'verack') if self.server: self.send_queue.put('fully_established') - elif m.command == b'inv': - inv = message.Inv.from_message(m) - logging.debug('%s:%s -> %s', self.host_print, self.port, inv) - to_get = inv.vectors.copy() - to_get.difference_update(shared.objects.keys()) - self.vectors_to_get.update(to_get) - # Do not send objects they already have. - self.vectors_to_send.difference_update(inv.vectors) - - elif m.command == b'object': - obj = structure.Object.from_message(m) - logging.debug('%s:%s -> %s', self.host_print, self.port, obj) - self.vectors_requested.pop(obj.vector, None) - self.vectors_to_get.discard(obj.vector) - if obj.is_valid() and obj.vector not in shared.objects: - with shared.objects_lock: - shared.objects[obj.vector] = obj - if ( - obj.object_type == shared.i2p_dest_obj_type - and obj.version == shared.i2p_dest_obj_version - ): - dest = base64.b64encode(obj.object_payload, altchars=b'-~') - logging.debug( - 'Received I2P destination object,' - ' adding to i2p_unchecked_node_pool') - logging.debug(dest) - shared.i2p_unchecked_node_pool.add((dest, 'i2p')) - shared.vector_advertise_queue.put(obj.vector) - - elif m.command == b'getdata': - getdata = message.GetData.from_message(m) - logging.debug('%s:%s -> %s', self.host_print, self.port, getdata) - self.vectors_to_send.update(getdata.vectors) - - elif m.command == b'addr': - addr = message.Addr.from_message(m) - logging.debug('%s:%s -> %s', self.host_print, self.port, addr) - for a in addr.addresses: - shared.unchecked_node_pool.add((a.host, a.port)) - elif m.command == b'ping': logging.debug('%s:%s -> ping', self.host_print, self.port) self.send_queue.put(message.Message(b'pong', b'')) @@ -440,7 +367,51 @@ class Connection(threading.Thread): shared.unchecked_node_pool.discard((self.host, self.port)) else: - logging.debug('%s:%s -> %s', self.host_print, self.port, m) + try: + getattr(self, '_process_msg_{}'.format(m.command.decode()))(m) + except (AttributeError, UnicodeDecodeError): + logging.debug('%s:%s -> %s', self.host_print, self.port, m) + + def _process_msg_version(self, m): + version = message.Version.from_message(m) + if shared.stream not in version.streams: + raise ValueError('message not for stream %i' % shared.stream) + logging.debug('%s:%s -> %s', self.host_print, self.port, version) + if ( + version.protocol_version != shared.protocol_version + or version.nonce == shared.nonce + ): + self.status = 'disconnecting' + self.send_queue.put(None) + else: + logging.info( + '%s:%s claims to be %s', + self.host_print, self.port, version.user_agent) + self.send_queue.put(message.Message(b'verack', b'')) + self.verack_sent = True + self.remote_version = version + if not self.server: + self.send_queue.put('fully_established') + if self.network == 'ip': + shared.address_advertise_queue.put(structure.NetAddr( + version.services, self.host, self.port)) + shared.node_pool.add((self.host, self.port)) + elif self.network == 'i2p': + shared.i2p_node_pool.add((self.host, 'i2p')) + if self.network == 'ip': + shared.address_advertise_queue.put(structure.NetAddr( + shared.services, version.host, shared.listening_port)) + if self.server: + if self.network == 'ip': + self.send_queue.put(message.Version(self.host, self.port)) + else: + self.send_queue.put(message.Version('127.0.0.1', 7656)) + + def _process_msg_addr(self, m): + addr = message.Addr.from_message(m) + logging.debug('%s:%s -> %s', self.host_print, self.port, addr) + for a in addr.addresses: + shared.unchecked_node_pool.add((a.host, a.port)) def _request_objects(self): if self.vectors_to_get and len(self.vectors_requested) < 100: @@ -499,4 +470,41 @@ class Connection(threading.Thread): message.Message(b'object', obj.to_bytes())) +class Connection(ConnectionBase): + """The connection with all commands implementation""" + def _process_msg_inv(self, m): + inv = message.Inv.from_message(m) + logging.debug('%s:%s -> %s', self.host_print, self.port, inv) + to_get = inv.vectors.copy() + to_get.difference_update(shared.objects.keys()) + self.vectors_to_get.update(to_get) + # Do not send objects they already have. + self.vectors_to_send.difference_update(inv.vectors) + + def _process_msg_object(self, m): + obj = structure.Object.from_message(m) + logging.debug('%s:%s -> %s', self.host_print, self.port, obj) + self.vectors_requested.pop(obj.vector, None) + self.vectors_to_get.discard(obj.vector) + if obj.is_valid() and obj.vector not in shared.objects: + with shared.objects_lock: + shared.objects[obj.vector] = obj + if ( + obj.object_type == shared.i2p_dest_obj_type + and obj.version == shared.i2p_dest_obj_version + ): + dest = base64.b64encode(obj.object_payload, altchars=b'-~') + logging.debug( + 'Received I2P destination object,' + ' adding to i2p_unchecked_node_pool') + logging.debug(dest) + shared.i2p_unchecked_node_pool.add((dest, 'i2p')) + shared.vector_advertise_queue.put(obj.vector) + + def _process_msg_getdata(self, m): + getdata = message.GetData.from_message(m) + logging.debug('%s:%s -> %s', self.host_print, self.port, getdata) + self.vectors_to_send.update(getdata.vectors) + + shared.connection = Connection -- 2.45.1 From b0fa19983871d8e25690fed7291a2cb9bdf59198 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 23 Aug 2023 02:42:34 +0300 Subject: [PATCH 02/12] A short test for normal connection (with timeout in 5 min) --- minode/tests/test_network.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 minode/tests/test_network.py diff --git a/minode/tests/test_network.py b/minode/tests/test_network.py new file mode 100644 index 0000000..6526aa1 --- /dev/null +++ b/minode/tests/test_network.py @@ -0,0 +1,69 @@ +"""Tests for network connections""" +import logging +import os +import random +import unittest +import tempfile +import time + +from minode import connection, main, shared +from minode.manager import Manager + + +logging.basicConfig( + level=logging.INFO, + format='[%(asctime)s] [%(levelname)s] %(message)s') + + +class TestNetwork(unittest.TestCase): + """Test case starting connections""" + + @classmethod + def setUpClass(cls): + shared.data_directory = tempfile.gettempdir() + + def setUp(self): + shared.core_nodes.clear() + shared.unchecked_node_pool.clear() + shared.objects = {} + try: + os.remove(os.path.join(shared.data_directory, 'objects.pickle')) + except FileNotFoundError: + pass + + def test_connection(self): + """Check a normal connection - should receive objects""" + Manager.load_data() + self.assertGreaterEqual(len(shared.core_nodes), 3) + + main.bootstrap_from_dns() + self.assertGreaterEqual(len(shared.unchecked_node_pool), 3) + + started = time.time() + nodes = list(shared.core_nodes.union(shared.unchecked_node_pool)) + random.shuffle(nodes) + + for node in nodes: + # unknown = node not in shared.node_pool + # self.assertTrue(unknown) + unknown = True + shared.node_pool.discard(node) + + c = connection.Connection(*node) + c.start() + connection_started = time.time() + while c.status not in ('disconnecting', 'disconnected', 'failed'): + # The addr of established connection is added to nodes pool + if unknown and c.status == 'fully_established': + unknown = False + self.assertIn(node, shared.node_pool) + if shared.objects or time.time() - connection_started > 90: + c.status = 'disconnecting' + if time.time() - started > 300: + c.status = 'disconnecting' + self.fail('Failed to receive an object in %s sec' % 300) + time.sleep(0.2) + if shared.objects: # got some objects + break + else: + self.fail('Failed to establish a proper connection') -- 2.45.1 From 7719de533828a7bf225a7013c64071a6deb778da Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Thu, 24 Aug 2023 00:23:54 +0300 Subject: [PATCH 03/12] Define a static method network_group() in NetAddrNoPrefix and use it in manager. --- minode/manager.py | 17 +++++++++-------- minode/structure.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/minode/manager.py b/minode/manager.py index 93a3f30..a03f9d6 100644 --- a/minode/manager.py +++ b/minode/manager.py @@ -77,7 +77,7 @@ class Manager(threading.Thread): with shared.connections_lock: shared.connections.remove(c) else: - hosts.add(c.host) + hosts.add(structure.NetAddrNoPrefix.network_group(c.host)) if not c.server: outgoing_connections += 1 @@ -121,15 +121,16 @@ class Manager(threading.Thread): else: to_connect.update(shared.i2p_node_pool) - for addr in to_connect: - if addr[0] in hosts: + for host, port in to_connect: + group = structure.NetAddrNoPrefix.network_group(host) + if group in hosts: continue - if addr[1] == 'i2p' and shared.i2p_enabled: - if shared.i2p_session_nick and addr[0] != shared.i2p_dest_pub: + if port == 'i2p' and shared.i2p_enabled: + if shared.i2p_session_nick and host != shared.i2p_dest_pub: try: d = I2PDialer( shared, - addr[0], shared.i2p_session_nick, + host, shared.i2p_session_nick, shared.i2p_sam_host, shared.i2p_sam_port) d.start() hosts.add(d.destination) @@ -141,9 +142,9 @@ class Manager(threading.Thread): else: continue else: - c = Connection(addr[0], addr[1]) + c = Connection(host, port) c.start() - hosts.add(c.host) + hosts.add(group) with shared.connections_lock: shared.connections.add(c) shared.hosts = hosts diff --git a/minode/structure.py b/minode/structure.py index 53785b8..405da1a 100644 --- a/minode/structure.py +++ b/minode/structure.py @@ -173,6 +173,21 @@ class NetAddrNoPrefix(): b += struct.pack('>H', int(self.port)) return b + @staticmethod + def network_group(host): + """A simplified network group identifier from pybitmessage protocol""" + try: + host = socket.inet_pton(socket.AF_INET, host) + return host[:2] + except socket.error: + try: + host = socket.inet_pton(socket.AF_INET6, host) + return host[:12] + except OSError: + return host + except TypeError: + return host + @classmethod def from_bytes(cls, b): services, host, port = struct.unpack('>Q16sH', b) -- 2.45.1 From abf062ac867be7f32b06915ba8377242ed33631c Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sun, 27 Aug 2023 03:12:17 +0300 Subject: [PATCH 04/12] Check network group of connections in process test if it isn't for i2p --- minode/tests/test_process.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/minode/tests/test_process.py b/minode/tests/test_process.py index b6c0b7e..4653f61 100644 --- a/minode/tests/test_process.py +++ b/minode/tests/test_process.py @@ -9,6 +9,8 @@ import time import psutil +from minode.structure import NetAddrNoPrefix + try: socket.socket().bind(('127.0.0.1', 7656)) i2p_port_free = True @@ -105,6 +107,12 @@ class TestProcess(TestProcessProto): if len(self.connections()) > self._connection_limit / 2: _time_to_connect = round(time.time() - _started) break + if '--i2p' not in self._process_cmd: + groups = [] + for c in self.connections(): + group = NetAddrNoPrefix.network_group(c.raddr[0]) + self.assertNotIn(group, groups) + groups.append(group) time.sleep(0.5) else: self.fail( -- 2.45.1 From 5ca6e8a3e36d22c2d8fcb2091bb0cd3d52ea2eb4 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 26 Aug 2023 19:13:05 +0300 Subject: [PATCH 05/12] Add a test for connections with large time offset --- minode/tests/test_network.py | 53 ++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/minode/tests/test_network.py b/minode/tests/test_network.py index 6526aa1..8f75048 100644 --- a/minode/tests/test_network.py +++ b/minode/tests/test_network.py @@ -5,6 +5,7 @@ import random import unittest import tempfile import time +from contextlib import contextmanager from minode import connection, main, shared from minode.manager import Manager @@ -15,6 +16,21 @@ logging.basicConfig( format='[%(asctime)s] [%(levelname)s] %(message)s') +@contextmanager +def time_offset(offset): + """ + Replace time.time() by a mock returning a constant value + with given offset from current time. + """ + started = time.time() + time_call = time.time + try: + time.time = lambda: started + offset + yield time_call + finally: + time.time = time_call + + class TestNetwork(unittest.TestCase): """Test case starting connections""" @@ -31,14 +47,17 @@ class TestNetwork(unittest.TestCase): except FileNotFoundError: pass - def test_connection(self): - """Check a normal connection - should receive objects""" + def _make_initial_nodes(self): Manager.load_data() self.assertGreaterEqual(len(shared.core_nodes), 3) main.bootstrap_from_dns() self.assertGreaterEqual(len(shared.unchecked_node_pool), 3) + def test_connection(self): + """Check a normal connection - should receive objects""" + self._make_initial_nodes() + started = time.time() nodes = list(shared.core_nodes.union(shared.unchecked_node_pool)) random.shuffle(nodes) @@ -67,3 +86,33 @@ class TestNetwork(unittest.TestCase): break else: self.fail('Failed to establish a proper connection') + + def test_time_offset(self): + """Assert the network bans for large time offset""" + def try_connect(nodes, timeout, call): + started = call() + for node in nodes: + c = connection.Connection(*node) + c.start() + while call() < started + timeout: + if c.status == 'fully_established': + return 'Established a connection' + if c.status in ('disconnected', 'failed'): + break + time.sleep(0.2) + else: + return 'Spent too much time trying to connect' + + def time_offset_connections(nodes, offset): + """Spoof time.time and open connections with given time offset""" + with time_offset(offset) as time_call: + result = try_connect(nodes, 200, time_call) + if result: + self.fail(result) + + self._make_initial_nodes() + nodes = random.sample( + tuple(shared.core_nodes.union(shared.unchecked_node_pool)), 5) + + time_offset_connections(nodes, 4000) + time_offset_connections(nodes, -4000) -- 2.45.1 From c4d22c4c2157a8fe3784f3b59cc4eda3dde1fd7c Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 28 Aug 2023 02:02:54 +0300 Subject: [PATCH 06/12] Add a test case for listener with a process running with --trusted-peer --- minode/tests/test_network.py | 88 +++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/minode/tests/test_network.py b/minode/tests/test_network.py index 8f75048..570c2e9 100644 --- a/minode/tests/test_network.py +++ b/minode/tests/test_network.py @@ -8,8 +8,11 @@ import time from contextlib import contextmanager from minode import connection, main, shared +from minode.listener import Listener from minode.manager import Manager +from .test_process import TestProcessProto + logging.basicConfig( level=logging.INFO, @@ -31,6 +34,27 @@ def time_offset(offset): time.time = time_call +@contextmanager +def run_listener(host='localhost', port=8444): + """ + Run the Listener with zero connection limit and + reset variables in shared after its stop. + """ + connection_limit = shared.connection_limit + shared.connection_limit = 0 + try: + listener = Listener(host, port) + listener.start() + yield listener + except OSError: + yield + finally: + shared.connection_limit = connection_limit + shared.connections.clear() + shared.shutting_down = True + time.sleep(1) + + class TestNetwork(unittest.TestCase): """Test case starting connections""" @@ -71,7 +95,7 @@ class TestNetwork(unittest.TestCase): c = connection.Connection(*node) c.start() connection_started = time.time() - while c.status not in ('disconnecting', 'disconnected', 'failed'): + while c.status not in ('disconnected', 'failed'): # The addr of established connection is added to nodes pool if unknown and c.status == 'fully_established': unknown = False @@ -116,3 +140,65 @@ class TestNetwork(unittest.TestCase): time_offset_connections(nodes, 4000) time_offset_connections(nodes, -4000) + + +class TestListener(TestProcessProto): + """A separate test case for Listener with a process with --trusted-peer""" + _process_cmd = ['minode', '--trusted-peer', '127.0.0.1'] + + def setUp(self): + shared.shutting_down = False + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + shared.shutting_down = False + + def test_listener(self): + """Start Listener and try to connect""" + with run_listener() as listener: + if not listener: + self.fail('Failed to start listener') + + c = connection.Connection('127.0.0.1', 8444) + shared.connections.add(c) + + for _ in range(30): + if len(shared.connections) > 1: + self.fail('The listener ignored connection limit') + time.sleep(0.5) + + shared.connection_limit = 2 + c.start() + started = time.time() + while c.status not in ('disconnected', 'failed'): + if c.status == 'fully_established': + self.fail('Connected to itself') + if time.time() - started > 90: + c.status = 'disconnecting' + time.sleep(0.2) + + server = None + started = time.time() + while not server: + time.sleep(0.2) + if time.time() - started > 90: + self.fail('Failed to establish the connection') + for c in shared.connections: + if c.status == 'fully_established': + server = c + self.assertTrue(server.server) + + while not self.process.connections(): + time.sleep(0.2) + if time.time() - started > 90: + self.fail('Failed to connect to listener') + + client = self.process.connections()[0] + self.assertEqual(client.raddr[0], '127.0.0.1') + self.assertEqual(client.raddr[1], 8444) + self.assertEqual(server.host, client.laddr[0]) + # self.assertEqual(server.port, client.laddr[1]) + server.status = 'disconnecting' + + self.assertFalse(listener.is_alive()) -- 2.45.1 From 1b9648f3ded7f562963a4158649f25f5678a5a0c Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Fri, 1 Sep 2023 06:03:45 +0300 Subject: [PATCH 07/12] Correct position of the except clause in listener loop --- minode/listener.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/minode/listener.py b/minode/listener.py index 88b26db..bec5365 100644 --- a/minode/listener.py +++ b/minode/listener.py @@ -28,13 +28,14 @@ class Listener(threading.Thread): break try: conn, addr = self.s.accept() - logging.info('Incoming connection from: %s:%i', *addr[:2]) - with shared.connections_lock: - if len(shared.connections) > shared.connection_limit: - conn.close() - else: - c = Connection(*addr[:2], conn, server=True) - c.start() - shared.connections.add(c) except socket.timeout: - pass + continue + + logging.info('Incoming connection from: %s:%i', *addr[:2]) + with shared.connections_lock: + if len(shared.connections) > shared.connection_limit: + conn.close() + else: + c = Connection(*addr[:2], conn, server=True) + c.start() + shared.connections.add(c) -- 2.45.1 From 908ed1f5826bdb6447c81bd666fd43b245916db6 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 4 Sep 2023 04:58:02 +0300 Subject: [PATCH 08/12] Run listener with a large time offset and ensure it's not connected --- minode/tests/test_network.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/minode/tests/test_network.py b/minode/tests/test_network.py index 570c2e9..c8cdd31 100644 --- a/minode/tests/test_network.py +++ b/minode/tests/test_network.py @@ -202,3 +202,16 @@ class TestListener(TestProcessProto): server.status = 'disconnecting' self.assertFalse(listener.is_alive()) + + def test_listener_timeoffset(self): + """Run listener with a large time offset - shouldn't connect""" + with time_offset(4000): + with run_listener() as listener: + if not listener: + self.fail('Failed to start listener') + shared.connection_limit = 2 + for _ in range(30): + for c in shared.connections: + if c.status == 'fully_established': + self.fail('Established a connection') + time.sleep(0.5) -- 2.45.1 From e11aece1a8192c39a5cdfd7f361ae20ae6211845 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Mon, 4 Sep 2023 05:07:56 +0300 Subject: [PATCH 09/12] Invalidate the version message with a large time offset --- minode/message.py | 7 +++++-- minode/tests/test_message.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/minode/message.py b/minode/message.py index eee5e3f..ee01ea8 100644 --- a/minode/message.py +++ b/minode/message.py @@ -145,10 +145,13 @@ class Version(): def from_message(cls, m): payload = m.payload - ( # unused: timestamp, net_addr_local - protocol_version, services, _, net_addr_remote, _, nonce + ( # unused: net_addr_local + protocol_version, services, timestamp, net_addr_remote, _, nonce ) = struct.unpack('>IQQ26s26s8s', payload[:80]) + if abs(time.time() - timestamp) > 3600: + raise ValueError('remote time offset is too large') + net_addr_remote = structure.NetAddrNoPrefix.from_bytes(net_addr_remote) host = net_addr_remote.host diff --git a/minode/tests/test_message.py b/minode/tests/test_message.py index 1a6c089..7c0511e 100644 --- a/minode/tests/test_message.py +++ b/minode/tests/test_message.py @@ -1,4 +1,6 @@ """Tests for messages""" +import struct +import time import unittest from binascii import unhexlify @@ -75,6 +77,13 @@ class TestMessage(unittest.TestCase): """Test version message""" msg = message.Message.from_bytes(sample_version_msg) self.assertEqual(msg.command, b'version') + with self.assertRaises(ValueError): + # large time offset + version_packet = message.Version.from_message(msg) + msg.payload = ( + msg.payload[:12] + struct.pack('>Q', int(time.time())) + + msg.payload[20:]) + version_packet = message.Version.from_message(msg) self.assertEqual(version_packet.host, '127.0.0.1') self.assertEqual(version_packet.port, 8444) -- 2.45.1 From aa6e8a57fbe7ca9002b713ad864e1d3629771fba Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Sat, 14 Oct 2023 04:49:49 +0300 Subject: [PATCH 10/12] Copy the relevant part of test_network_group() from PyBitmessage --- minode/tests/test_structure.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/minode/tests/test_structure.py b/minode/tests/test_structure.py index 16e41fe..970c152 100644 --- a/minode/tests/test_structure.py +++ b/minode/tests/test_structure.py @@ -104,6 +104,27 @@ class TestStructure(unittest.TestCase): addr = structure.NetAddr(1, '2607:5300:201:3000::57ae', 8080, 1) self.assertEqual(addr.to_bytes()[8:], sample_addr_data[8:]) + def test_network_group(self): + """Test various types of network groups""" + test_ip = '1.2.3.4' + self.assertEqual( + b'\x01\x02', structure.NetAddrNoPrefix.network_group(test_ip)) + self.assertEqual( + structure.NetAddrNoPrefix.network_group('8.8.8.8'), + structure.NetAddrNoPrefix.network_group('8.8.4.4')) + self.assertNotEqual( + structure.NetAddrNoPrefix.network_group('1.1.1.1'), + structure.NetAddrNoPrefix.network_group('8.8.8.8')) + test_ip = '0102:0304:0506:0708:090A:0B0C:0D0E:0F10' + self.assertEqual( + b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C', + structure.NetAddrNoPrefix.network_group(test_ip)) + for test_ip in ( + 'bootstrap8444.bitmessage.org', 'quzwelsuziwqgpt2.onion', None + ): + self.assertEqual( + test_ip, structure.NetAddrNoPrefix.network_group(test_ip)) + def test_object(self): """Create and check objects""" obj = structure.Object.from_message( -- 2.45.1 From 16031874c7b3ab4af1b3206312817c6ef4178366 Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Tue, 9 Jul 2024 00:39:41 +0300 Subject: [PATCH 11/12] Relax the condition in TestProcess.test_connections() --- minode/tests/test_process.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/minode/tests/test_process.py b/minode/tests/test_process.py index 4653f61..e6a391c 100644 --- a/minode/tests/test_process.py +++ b/minode/tests/test_process.py @@ -21,7 +21,7 @@ except (OSError, socket.error): class TestProcessProto(unittest.TestCase): """Test process attributes, common flow""" _process_cmd = ['minode'] - _connection_limit = 4 if sys.platform.startswith('win') else 10 + _connection_limit = 4 if sys.platform.startswith('win') else 8 _listen = False _listening_port = None @@ -84,7 +84,7 @@ class TestProcessShutdown(TestProcessProto): class TestProcess(TestProcessProto): """The test case for minode process""" - _wait_time = 120 + _wait_time = 180 _check_limit = False def test_connections(self): @@ -104,7 +104,7 @@ class TestProcess(TestProcessProto): time.sleep(1) for _ in range(self._wait_time * 2): - if len(self.connections()) > self._connection_limit / 2: + if len(self.connections()) >= self._connection_limit / 2: _time_to_connect = round(time.time() - _started) break if '--i2p' not in self._process_cmd: @@ -116,7 +116,7 @@ class TestProcess(TestProcessProto): time.sleep(0.5) else: self.fail( - 'Failed establish at least %i connections in %s sec' + 'Failed to establish at least %i connections in %s sec' % (int(self._connection_limit / 2), self._wait_time)) if self._check_limit: -- 2.45.1 From a451a255afd2de7e7affbfca275bcbd32032f5aa Mon Sep 17 00:00:00 2001 From: Lee Miller Date: Wed, 22 May 2024 22:20:50 +0300 Subject: [PATCH 12/12] Bump version to 0.3.3 --- minode/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minode/shared.py b/minode/shared.py index d49786c..72864ec 100644 --- a/minode/shared.py +++ b/minode/shared.py @@ -21,7 +21,7 @@ protocol_version = 3 services = 3 # NODE_NETWORK, NODE_SSL stream = 1 nonce = os.urandom(8) -user_agent = b'/MiNode:0.3.2/' +user_agent = b'/MiNode:0.3.3/' timeout = 600 header_length = 24 i2p_dest_obj_type = 0x493250 -- 2.45.1