mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-20 13:28:20 +02:00
186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
# MIT License
|
|
#
|
|
# Copyright (c) 2020 Jonathan Zernik
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
import logging
|
|
import queue
|
|
import socket
|
|
import threading
|
|
from contextlib import contextmanager
|
|
from typing import Dict
|
|
from typing import List
|
|
from typing import Optional
|
|
|
|
from squeaknode.core.peer_address import PeerAddress
|
|
from squeaknode.network.connection import Connection
|
|
from squeaknode.network.peer import Peer
|
|
from squeaknode.network.peer_client import ConnectPeerResult
|
|
from squeaknode.node.listener_subscription_client import EventListener
|
|
from squeaknode.node.squeak_controller import SqueakController
|
|
|
|
|
|
MIN_PEERS = 5
|
|
MAX_PEERS = 10
|
|
UPDATE_THREAD_SLEEP_TIME = 10
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ConnectionManager(object):
|
|
"""Maintains connections to other peers in the network.
|
|
"""
|
|
|
|
def __init__(self, local_address):
|
|
self._peers: Dict[PeerAddress, Peer] = {}
|
|
self.peers_lock = threading.Lock()
|
|
self.peer_changed_listener = EventListener()
|
|
self.single_peer_changed_listener = EventListener()
|
|
self.accept_connections = True
|
|
self.local_address = local_address
|
|
|
|
@contextmanager
|
|
def connect(
|
|
self,
|
|
peer_socket: socket.socket,
|
|
address: PeerAddress,
|
|
outgoing: bool,
|
|
squeak_controller: SqueakController,
|
|
result_queue: queue.Queue,
|
|
):
|
|
try:
|
|
peer = Peer(
|
|
peer_socket,
|
|
self.local_address,
|
|
address,
|
|
outgoing,
|
|
self.single_peer_changed_listener,
|
|
)
|
|
connection = Connection(peer, squeak_controller)
|
|
logger.debug("Doing handshake.")
|
|
connection.handshake()
|
|
logger.debug("Adding peer.")
|
|
self._add_peer(peer)
|
|
result_queue.put(
|
|
ConnectPeerResult.from_success(peer.remote_address))
|
|
logger.debug("Yielding connection.")
|
|
yield connection
|
|
logger.debug("Removing peer.")
|
|
self._remove_peer(peer)
|
|
except Exception as e:
|
|
result_queue.put(ConnectPeerResult.from_failure(e))
|
|
raise
|
|
finally:
|
|
peer.stop()
|
|
|
|
@property
|
|
def peers(self) -> List[Peer]:
|
|
return list(self._peers.values())
|
|
|
|
def has_connection(self, address):
|
|
"""Return True if the address is already connected."""
|
|
return address in self._peers
|
|
|
|
def _on_peers_changed(self):
|
|
peers = self.peers
|
|
logger.info('Current number of peers {}'.format(len(peers)))
|
|
logger.info('Current peers:--------')
|
|
for peer in peers:
|
|
logger.info(peer)
|
|
logger.info('--------------')
|
|
self.peer_changed_listener.handle_new_item(peers)
|
|
|
|
def _is_duplicate_nonce(self, peer):
|
|
for other_peer in self.peers:
|
|
if other_peer.local_version:
|
|
if peer.remote_version == other_peer.local_version.nNonce:
|
|
return True
|
|
return False
|
|
|
|
def _add_peer(self, peer: Peer):
|
|
"""Add a peer.
|
|
"""
|
|
with self.peers_lock:
|
|
if not self.accept_connections:
|
|
raise NotAcceptingConnectionsError()
|
|
if self._is_duplicate_nonce(peer):
|
|
logger.debug('Failed to add peer {}'.format(peer))
|
|
raise DuplicateNonceError()
|
|
if self.has_connection(peer.remote_address):
|
|
logger.debug('Failed to add peer {}'.format(peer))
|
|
raise DuplicatePeerError()
|
|
self._peers[peer.remote_address] = peer
|
|
logger.debug('Added peer {}'.format(peer))
|
|
self._on_peers_changed()
|
|
|
|
def _remove_peer(self, peer: Peer):
|
|
"""Remove a peer.
|
|
"""
|
|
with self.peers_lock:
|
|
if not self.has_connection(peer.remote_address):
|
|
return
|
|
del self._peers[peer.remote_address]
|
|
logger.debug('Removed peer {}'.format(peer))
|
|
self._on_peers_changed()
|
|
|
|
def get_peer(self, address) -> Optional[Peer]:
|
|
"""Get a peer info by address.
|
|
"""
|
|
return self._peers.get(address)
|
|
|
|
def stop_connection(self, address):
|
|
"""Stop peer connections for address.
|
|
"""
|
|
with self.peers_lock:
|
|
peer = self.get_peer(address)
|
|
if peer is not None:
|
|
peer.stop()
|
|
|
|
def stop_all_connections(self):
|
|
"""Stop all peer connections.
|
|
"""
|
|
self.accept_connections = False
|
|
with self.peers_lock:
|
|
for peer in self.peers:
|
|
peer.stop()
|
|
|
|
def yield_peers_changed(self, stopped: threading.Event):
|
|
yield from self.peer_changed_listener.yield_items(stopped)
|
|
|
|
def yield_single_peer_changed(self, peer_address: PeerAddress, stopped: threading.Event):
|
|
for peer in self.single_peer_changed_listener.yield_items(stopped):
|
|
logger.debug('yield_single_peer_changed: {}'.format(peer))
|
|
if peer.remote_address == peer_address:
|
|
if peer.connect_time is None:
|
|
yield None
|
|
else:
|
|
yield peer
|
|
|
|
|
|
class DuplicatePeerError(Exception):
|
|
pass
|
|
|
|
|
|
class DuplicateNonceError(Exception):
|
|
pass
|
|
|
|
|
|
class NotAcceptingConnectionsError(Exception):
|
|
pass
|