diff --git a/Simulation/__pycache__/library.cpython-313.pyc b/Simulation/__pycache__/library.cpython-313.pyc new file mode 100644 index 0000000..72493a7 Binary files /dev/null and b/Simulation/__pycache__/library.cpython-313.pyc differ diff --git a/Simulation/library.py b/Simulation/library.py new file mode 100644 index 0000000..a1e5209 --- /dev/null +++ b/Simulation/library.py @@ -0,0 +1,143 @@ +import ctypes +from ctypes import Structure, c_char_p, c_int, cdll +from dataclasses import dataclass, astuple +import time +import json +from urllib.parse import urlparse +from typing import List +import subprocess +import uuid +import dacite +import os +import sqlalchemy +import dataclasses +from typing import Union +import schedule +import threading +import requests +import subprocess + +# p2prc.Init("") + +# Start running schedule +schedule.run_pending() + +# # Create Sqlite database to track processes +# engine=sqlalchemy.create_engine(f'sqlite:///homeserver.db') + +# Global variable +# A global variable will be populated on runtime. +# It is read from P2PRC directly or from a local +# database. + +# Node information +# Generated using: https://jsonformatter.org/json-to-python +@dataclass +class Node: + Name: str + MachineUsername: str + IPV4: str + IPV6: str + Latency: int + Download: int + Upload: int + ServerPort: str + BareMetalSSHPort: str + NAT: bool + EscapeImplementation: str + ProxyServer: bool + UnSafeMode: bool + PublicKey: str + CustomInformation: str + +@dataclass +class IPAddress: + ip_address: List[Node] + + +# ---------------------------------------------------------------------------- +# ----------------------------- Helper functions ----------------------------- +# ---------------------------------------------------------------------------- + +p2prc = None + +def LoadDLL(envPath=""): + global p2prc + # os.environ["P2PRC"] = envPath + print(envPath) + # env = "P2PRC=" + envPath + os.system("sh setEnv.sh " + envPath) + # subprocess.call("sh setEnv.sh " + envPath) + p2prc = ctypes.CDLL("SharedObjects/p2prc.so") + + +def StartServer(): + # Starting P2PRC as a server mode + p2prc.Server() + +def Init(): + p2prc.Init("") + +# Class to create string to pass as string function +# parameter to shared object file +class go_string(Structure): + _fields_ = [ + ("p", c_char_p), + ("n", c_int)] + +# Ensuring local port can escape NAT and responds +# with the public ip and port no. +# If the domain name is specified then the public IP +# can be used as an A name entry. +def P2PRCMapPort(port="",domainname="",serveraddress=""): + port = go_string(c_char_p(port.encode('utf-8')), len(port)) + domainname = go_string(c_char_p(domainname.encode('utf-8')), len(domainname)) + serveraddress = go_string(c_char_p(serveraddress.encode('utf-8')), len(serveraddress)) + + # Defining the response type of the GoLang function + # function + p2prc.MapPort.restype = c_char_p + # Calling the Go function + address = p2prc.MapPort(port,domainname,serveraddress) + res = str(address).strip("b'") + return res + +# Lists all avaliable nodes in the network +def ListNodes(): + # View IP Table information + p2prc.ViewIPTable.restype = c_char_p + ipTable = p2prc.ViewIPTable() + # View IP Table as + ipTableObject = json.loads((str(ipTable).strip("b'"))) + dat: IPAddress = dacite.from_dict(IPAddress,ipTableObject) + return dat + +# Add a root node to P2RRC and overwrites all other nodes. +# To be only added before the network started and with +# the intention of a fresh protocol. +def AddRootNode(ip="", port="") -> bool: + ip = go_string(c_char_p(ip.encode('utf-8')), len(ip)) + port = go_string(c_char_p(port.encode('utf-8')), len(port)) + + p2prc.AddRootNode.restype = c_char_p + res = p2prc.AddRootNode(ip, port) + if str(res).strip("b'") == "Success": + return True + return False + + + +# python function to pass-through custom +# information to interpret which can be +# interpreted as a DSL. +def AddCustomInformation(message="") -> bool: + message = go_string(c_char_p(message.encode('utf-8')), len(message)) + + p2prc.CustomInformation.restype = c_char_p + status = p2prc.CustomInformation(message) + if str(status).strip("b'") == "Success": + return True + return False + + + diff --git a/Simulation/requests b/Simulation/requests new file mode 100644 index 0000000..7a7cc93 --- /dev/null +++ b/Simulation/requests @@ -0,0 +1,6 @@ +dacite +schedule +sqlalchemy +dacite +schedule +sqlalchemy diff --git a/Simulation/requirements.txt b/Simulation/requirements.txt new file mode 100644 index 0000000..c163bff --- /dev/null +++ b/Simulation/requirements.txt @@ -0,0 +1,4 @@ +dacite +schedule +sqlalchemy +requests diff --git a/Simulation/setEnv.sh b/Simulation/setEnv.sh new file mode 100644 index 0000000..8bfb77e --- /dev/null +++ b/Simulation/setEnv.sh @@ -0,0 +1 @@ +export P2PRC=$1 \ No newline at end of file diff --git a/Simulation/simulation.py b/Simulation/simulation.py new file mode 100644 index 0000000..ba10e24 --- /dev/null +++ b/Simulation/simulation.py @@ -0,0 +1,145 @@ +# This repo consists of the P2PRC simulation to ensure +# that we can test various nodes locally with just processes. +# Note: This implementation is for local nodes only. + +from library import * +import os +import socket +from contextlib import closing + +# Stores the base information +# required to know about a P2PRC node +@dataclass +class P2PRCNode: + Name: str + ConfigLocation: str + ServerPort: str + RunningState: bool + RootNodePort: str + +@dataclass +class P2PRCNodes: + P2PRCNodes: List[P2PRCNode] + +# Tracker for P2PRC nodes +PublicP2PRCNodes: Union[P2PRCNodes, None] = None + +# Tracker of root Node +RootNode: P2PRCNode + +# Sets up P2PRC with default configuration +def SetupP2PRCNode(name=""): + # Unset environment variables + os.environ.pop('P2PRC', None) + # Create Test folder + Dirname = "P2PRC-Test-" + name + try: + os.mkdir(Dirname) + print(f"Directory '{Dirname}' created successfully.") + except FileExistsError: + print(f"Directory '{Dirname}' already exists.") + KillProcedure() + + # Set env variable for session + envPath = os.path.abspath(os.getcwd()) + "/" + Dirname + LoadDLL(envPath) + + # Generate random 4 + ServerPortNo = str(find_free_port()) + + # Initialise data class + p2prcNode = P2PRCNode( + Name = name, + ConfigLocation = envPath, + ServerPort = ServerPortNo, + RunningState = True, + RootNodePort = "", + ) + + # Adds P2PRC node to memory + AddP2PRCNodeToMemory(p2prcNode) + + # Initialise P2PRC instance + Init() + + # Add root node to ip table + AddRootNode("0.0.0.0",ServerPortNo) + + # Change server port no on Config + ChangeFileValue(envPath + "/config.json", "ServerPort", ServerPortNo) + + thread = threading.Thread(target = StartServer) + thread.start() + + +# Find free TCP port avaliable +# Source: https://stackoverflow.com/questions/1365265/on-localhost-how-do-i-pick-a-free-port-number +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + +# Standard function called after +# a simulation is terminated or +# when error exception is called +def KillProcedure(): + RemoveAllP2PRCNodesFromMemoryAndConfigFile() + +# -------------------------- helper functions ----------------------- +def ChangeFileValue(file_path, key, new_value): + """ + Reads a JSON file, updates a key with a new value, and writes it back. + + :param file_path: Path to the JSON file + :param key: Key in the JSON to update + :param new_value: New value to assign to the key + """ + # Read from the JSON file + with open(file_path, 'r') as file: + data = json.load(file) + + # Update the key with the new value + data[key] = new_value + + # Write the updated data back to the JSON file + with open(file_path, 'w') as file: + json.dump(data, file, indent=4) + +# Adds the process dataclass the to +# the global variable PublicProcess. +def AddP2PRCNodeToMemory(p2prcNode: P2PRCNode): + global PublicP2PRCNodes + global RootNode + if PublicP2PRCNodes == None: + PublicP2PRCNodes = P2PRCNodes(P2PRCNodes=[p2prcNode]) + p2prcNode.RootNodePort = p2prcNode.ServerPort + RootNode = p2prcNode + else: + p2prcNode.RootNodePort = RootNode.ServerPort + PublicP2PRCNodes.P2PRCNodes.append(p2prcNode) + +# Remove all nodes test nodes generated +def RemoveAllP2PRCNodesFromMemoryAndConfigFile(): + global PublicP2PRCNodes + if PublicP2PRCNodes is not None: + for node in PublicP2PRCNodes.P2PRCNodes: + config_path = node.ConfigLocation + # Safely remove the folder if it exists + if os.path.exists(config_path) and os.path.isdir(config_path): + try: + shutil.rmtree(config_path) + print(f"Deleted folder: {config_path}") + except Exception as e: + print(f"Failed to delete {config_path}: {e}") + # Clear the node list + PublicP2PRCNodes = None + print("All P2PRC nodes removed from memory.") + else: + print("No P2PRC nodes to remove.") + + +if __name__ == "__main__": + # Setting up 2 P2PRC nodes + SetupP2PRCNode(name="Test") + SetupP2PRCNode(name="Test1") \ No newline at end of file diff --git a/Simulation/test.py b/Simulation/test.py new file mode 100644 index 0000000..e094ba4 --- /dev/null +++ b/Simulation/test.py @@ -0,0 +1,14 @@ +from library import * + + +if __name__ == "__main__": + P2PRCNodes = ListNodes() + # Print nodes in the network + print(P2PRCNodes) + + # Add custom information to the network + if AddCustomInformation("Test"): + print("It worked") + + if AddRootNode("0.0.0.0", "8081"): + print("It worked for adding root node")