# # Ham'n'Cheese # Copyright (C) 2023-2024 Scott Duensing # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 3 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see # extends Node signal peer_received_packet(peer, verb, payload) var _tcp := TCPServer.new() var _basePort : int var _serverRunning: bool var _timer : Timer # This array is of dictionary elements that contain: # "id": Unique ID of this peer (1-254) based on IP. # "ip": IP address of this peer. # "port": Listen port of this peer. # "user": User name on this IP. # "uuid": UUID for this peer. var _userInfo: Dictionary # This array is of dictionary elements that contain: # "tcp": TCP connection to this peer. # "id": Unique ID of this peer (1-254) based on IP. # "ip": IP address of this peer. # "online": Will be true when TCP network can talk to the peer. # "port": Listen port of this peer. # "user": User name on this IP. # "uuid": UUID for this peer. # "refresh": Do we need to redraw this entry? # # In addition, it contains all the properties of the C++ peer list: # "_tag": Sequence number for communicating with the management API. # "_type": Either "pSp" (via supernode) or "p2p" (direct) connection. # "ip4addr": IP address of this peer with subnet in CIDR notation. # "purgeable": Can this peer be removed from the list if needed? # "local": (unknown) # "macaddr": MAC address of the peer. # "sockaddr": IP and socket of connected supernode. # "desc": Description of this peer, usually the machine name. # "last_p2p": Last P2P packet time. # "last_sent_query": (unknown) # "last_seen": Last time this peer was seen on the network. var peerArray: Array func _on_timer_timeout(): if _serverRunning: # Tell everyone we're alive. for peer in peerArray: if peer["tcp"] != null: peer["tcp"].poll() if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: send_to_peer(peer, "status", { "online": true, "user": _userInfo["user"], "uuid": _userInfo["uuid"] }) func _process(_delta): if _serverRunning: _tcp_server() func _sort_by_ip(a, b): # Sort numerically. if Util.ip_string_to_int(a["ip"]) < Util.ip_string_to_int(b["ip"]): return true return false func _tcp_server(): if _tcp.is_listening(): # New inbound connection. if _tcp.is_connection_available(): var new_peer: StreamPeerTCP = _tcp.take_connection() # Do we have a connection for this peer already? for peer in peerArray: if peer["ip"] == new_peer.get_connected_host(): if peer["tcp"] == null: # No - add it! peer["tcp"] = new_peer print("New peer ", peer["ip"], ":", peer["port"]) else: # Yes. Close existing, use this one. peer["tcp"].disconnect_from_host() peer["tcp"] = new_peer print("Updated peer ", peer["ip"], ":", peer["port"]) peer["tcp"].poll() if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: peer["tcp"].set_no_delay(true) # ***TODO*** This throws an error some times. # Process incoming data and attempt connections. for peer in peerArray: if peer["tcp"] == null: # Try to connect. print("Connecting to ", peer["ip"], ":", peer["port"]) peer["tcp"] = StreamPeerTCP.new() var error = peer["tcp"].connect_to_host(peer["ip"], peer["port"]) if error == OK: peer["tcp"].set_no_delay(true) else: peer["tcp"] = null else: # Update network status. peer["tcp"].poll() # Still connected? if peer["tcp"].get_status() == StreamPeerTCP.STATUS_ERROR: peer["tcp"].disconnect_from_host() peer["tcp"] = null print("Lost peer ", peer["ip"], ":", peer["port"]) else: # Data waiting? if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: if peer["tcp"].get_available_bytes() > 0: var data = peer["tcp"].get_var() peer_received_packet.emit(peer, data["verb"], data["payload"]) func clear(): peerArray.clear() func disconnect_peer(peer): if peer["tcp"] != null: send_to_peer(peer, "status", { "online": false }) peer["tcp"].disconnect_from_host() peer["tcp"] = null print("Disconnected peer ", peer["ip"], ":", peer["port"]) func is_running(): return _serverRunning func send_to_peer(peer, verb: String, payload: Dictionary): if peer["tcp"] != null: if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: var message = {} message["verb"] = verb message["payload"] = payload peer["tcp"].put_var(message) peer["tcp"].poll() func start_server(address, portStart, user, uuid): if !_serverRunning: clear() # We are creating the peer port number by combining the portStart with the # last octet of the peer's VPN IP address. Since this octet cannot be zero, # we actually use portStart-1 as the base so we don't waste the first port # specified by portStart. _basePort = portStart - 1 _userInfo["ip"] = address _userInfo["user"] = user _userInfo["uuid"] = uuid _userInfo["id"] = _userInfo["ip"].get_slice(".", 3).to_int() # Use last octet for our ID. _userInfo["port"] = _basePort + _userInfo["id"] # Set up our listening port. _tcp.listen(_userInfo["port"], _userInfo["ip"]) _serverRunning = true func startup(): _serverRunning = false clear() _timer = Timer.new() _timer.wait_time = 1 _timer.one_shot = false _timer.timeout.connect(_on_timer_timeout) Engine.get_main_loop().current_scene.add_child(_timer) _timer.start() func stop_server(): if _serverRunning: # Tear down network. _serverRunning = false for peer in peerArray: disconnect_peer(peer) _tcp.stop() func update(peersFromCPP: Array): var found var changed = false var oldArray: Array = [] if !_serverRunning: return #print("\nPeers Raw: ", peersFromCPP) #print("Peers Before: ", peerArray) # Remember what we started with. oldArray = peerArray.duplicate(true) # Start a new peer list. peerArray.clear() # Add everyone connected. for peerCPP in peersFromCPP: if peerCPP["ip4addr"] == "": continue # Get an IP without netmask. peerCPP["ip"] = peerCPP["ip4addr"].get_slice("/", 0) # Do we have this peer's information from before? found = false for oldPeer in oldArray: if oldPeer["ip"] == peerCPP["ip"]: found = true # Sometimes we get duplicates, prevent adding a peer twice. for knownPeer in peerArray: if knownPeer["ip"] == oldPeer["ip"]: continue peerCPP = oldPeer if !found: # We didn't have them. Add needed records. peerCPP["user"] = "" peerCPP["uuid"] = "" peerCPP["online"] = false peerCPP["refresh"] = false changed = true # Also create the TCP connection data. peerCPP["id"] = peerCPP["ip"].get_slice(".", 3).to_int() # Use last octet for our ID. peerCPP["port"] = _basePort + peerCPP["ip"].get_slice(".", 3).to_int() peerCPP["tcp"] = null # Add them to the new list. peerArray.append(peerCPP) # Check if we need to redraw anyone. for peer in peerArray: if peer["refresh"] == true: peer["refresh"] = false changed = true # Sort new array. peerArray.sort_custom(_sort_by_ip) #print("Peers After: ", peerArray) return changed