From 9f4cb9af0293dfcd3e62cfe26c1ef8f7210fec37 Mon Sep 17 00:00:00 2001 From: Akilan Date: Tue, 29 Apr 2025 23:28:33 +0100 Subject: [PATCH] added base simulation --- .../__pycache__/library.cpython-313.pyc | Bin 0 -> 5132 bytes Simulation/library.py | 143 +++++++++++++++++ Simulation/requests | 6 + Simulation/requirements.txt | 4 + Simulation/setEnv.sh | 1 + Simulation/simulation.py | 145 ++++++++++++++++++ Simulation/test.py | 14 ++ 7 files changed, 313 insertions(+) create mode 100644 Simulation/__pycache__/library.cpython-313.pyc create mode 100644 Simulation/library.py create mode 100644 Simulation/requests create mode 100644 Simulation/requirements.txt create mode 100644 Simulation/setEnv.sh create mode 100644 Simulation/simulation.py create mode 100644 Simulation/test.py diff --git a/Simulation/__pycache__/library.cpython-313.pyc b/Simulation/__pycache__/library.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72493a7da47c3384515a21eb33299406e2c7a929 GIT binary patch literal 5132 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 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")