From 3d4dae716e2aa1a17dd07e52ef7bb3992fe7bf70 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 16:58:06 +0100 Subject: [PATCH 001/112] Add real-world and white-box coordinators for NetSecGame - Implemented NSERealWorldGameCoordinator to execute actions in the real world using nmap for network scanning and service discovery. - Added WhiteBoxNSGCoordinator to provide a comprehensive list of possible actions for agents, including scanning networks, finding services, exploiting services, and data transfers. - Introduced command-line argument parsing for both coordinators to configure logging levels, game host, game port, and task configuration. - Created an empty __init__.py file in the worlds directory to facilitate module imports. --- AIDojoCoordinator/{worlds => coordinator}/__init__.py | 0 AIDojoCoordinator/{ => coordinator}/coordinator.py | 2 +- AIDojoCoordinator/{ => coordinator}/global_defender.py | 0 AIDojoCoordinator/{ => coordinator}/netsecenv_conf.yaml | 0 AIDojoCoordinator/{ => coordinator}/worlds/CYSTCoordinator.py | 2 +- .../{ => coordinator}/worlds/NSEGameCoordinator.py | 2 +- .../{ => coordinator}/worlds/NSGRealWorldCoordinator.py | 0 .../{ => coordinator}/worlds/WhiteBoxNSGCoordinator.py | 0 AIDojoCoordinator/coordinator/worlds/__init__.py | 0 NetSecGameAgents | 2 +- tests/coordinator/test_agent_server.py | 2 +- tests/coordinator/test_coordinator_core.py | 2 +- tests/coordinator/test_global_defender.py | 2 +- 13 files changed, 7 insertions(+), 7 deletions(-) rename AIDojoCoordinator/{worlds => coordinator}/__init__.py (100%) rename AIDojoCoordinator/{ => coordinator}/coordinator.py (99%) rename AIDojoCoordinator/{ => coordinator}/global_defender.py (100%) rename AIDojoCoordinator/{ => coordinator}/netsecenv_conf.yaml (100%) rename AIDojoCoordinator/{ => coordinator}/worlds/CYSTCoordinator.py (99%) rename AIDojoCoordinator/{ => coordinator}/worlds/NSEGameCoordinator.py (99%) rename AIDojoCoordinator/{ => coordinator}/worlds/NSGRealWorldCoordinator.py (100%) rename AIDojoCoordinator/{ => coordinator}/worlds/WhiteBoxNSGCoordinator.py (100%) create mode 100644 AIDojoCoordinator/coordinator/worlds/__init__.py diff --git a/AIDojoCoordinator/worlds/__init__.py b/AIDojoCoordinator/coordinator/__init__.py similarity index 100% rename from AIDojoCoordinator/worlds/__init__.py rename to AIDojoCoordinator/coordinator/__init__.py diff --git a/AIDojoCoordinator/coordinator.py b/AIDojoCoordinator/coordinator/coordinator.py similarity index 99% rename from AIDojoCoordinator/coordinator.py rename to AIDojoCoordinator/coordinator/coordinator.py index 40e771c2..5075cb8f 100644 --- a/AIDojoCoordinator/coordinator.py +++ b/AIDojoCoordinator/coordinator/coordinator.py @@ -5,7 +5,7 @@ import signal from AIDojoCoordinator.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig -from AIDojoCoordinator.global_defender import GlobalDefender +from AIDojoCoordinator.coordinator.global_defender import GlobalDefender from AIDojoCoordinator.utils.utils import observation_as_dict, get_str_hash, ConfigParser, store_trajectories_to_jsonl import os from aiohttp import ClientSession diff --git a/AIDojoCoordinator/global_defender.py b/AIDojoCoordinator/coordinator/global_defender.py similarity index 100% rename from AIDojoCoordinator/global_defender.py rename to AIDojoCoordinator/coordinator/global_defender.py diff --git a/AIDojoCoordinator/netsecenv_conf.yaml b/AIDojoCoordinator/coordinator/netsecenv_conf.yaml similarity index 100% rename from AIDojoCoordinator/netsecenv_conf.yaml rename to AIDojoCoordinator/coordinator/netsecenv_conf.yaml diff --git a/AIDojoCoordinator/worlds/CYSTCoordinator.py b/AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py similarity index 99% rename from AIDojoCoordinator/worlds/CYSTCoordinator.py rename to AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py index f9d65c74..6f65df73 100644 --- a/AIDojoCoordinator/worlds/CYSTCoordinator.py +++ b/AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py @@ -9,7 +9,7 @@ import argparse from pathlib import Path from AIDojoCoordinator.game_components import GameState, Action, ActionType, IP, Service -from AIDojoCoordinator.coordinator import GameCoordinator +from AIDojoCoordinator.coordinator.coordinator import GameCoordinator from AIDojoCoordinator.utils.utils import get_starting_position_from_cyst_config, get_logging_level diff --git a/AIDojoCoordinator/worlds/NSEGameCoordinator.py b/AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py similarity index 99% rename from AIDojoCoordinator/worlds/NSEGameCoordinator.py rename to AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py index 76222313..f67bb4ed 100644 --- a/AIDojoCoordinator/worlds/NSEGameCoordinator.py +++ b/AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py @@ -13,7 +13,7 @@ from collections import defaultdict from AIDojoCoordinator.game_components import GameState, Action, ActionType, IP, Network, Data, Service -from AIDojoCoordinator.coordinator import GameCoordinator +from AIDojoCoordinator.coordinator.coordinator import GameCoordinator from cyst.api.configuration import NodeConfig, RouterConfig, ConnectionConfig, ExploitConfig, FirewallPolicy from AIDojoCoordinator.utils.utils import get_logging_level diff --git a/AIDojoCoordinator/worlds/NSGRealWorldCoordinator.py b/AIDojoCoordinator/coordinator/worlds/NSGRealWorldCoordinator.py similarity index 100% rename from AIDojoCoordinator/worlds/NSGRealWorldCoordinator.py rename to AIDojoCoordinator/coordinator/worlds/NSGRealWorldCoordinator.py diff --git a/AIDojoCoordinator/worlds/WhiteBoxNSGCoordinator.py b/AIDojoCoordinator/coordinator/worlds/WhiteBoxNSGCoordinator.py similarity index 100% rename from AIDojoCoordinator/worlds/WhiteBoxNSGCoordinator.py rename to AIDojoCoordinator/coordinator/worlds/WhiteBoxNSGCoordinator.py diff --git a/AIDojoCoordinator/coordinator/worlds/__init__.py b/AIDojoCoordinator/coordinator/worlds/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/NetSecGameAgents b/NetSecGameAgents index 3fa45825..8ac0af82 160000 --- a/NetSecGameAgents +++ b/NetSecGameAgents @@ -1 +1 @@ -Subproject commit 3fa45825b466e9555aa466e4126b3adc1e9c668d +Subproject commit 8ac0af82d6d9769e73aab3926ff98ee996ed66d2 diff --git a/tests/coordinator/test_agent_server.py b/tests/coordinator/test_agent_server.py index 99c2f651..5c90ea98 100644 --- a/tests/coordinator/test_agent_server.py +++ b/tests/coordinator/test_agent_server.py @@ -3,7 +3,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock from contextlib import suppress -from AIDojoCoordinator.coordinator import AgentServer +from AIDojoCoordinator.coordinator.coordinator import AgentServer from AIDojoCoordinator.game_components import Action, ActionType, ProtocolConfig # ----------------------- diff --git a/tests/coordinator/test_coordinator_core.py b/tests/coordinator/test_coordinator_core.py index 0853693a..1af7b92d 100644 --- a/tests/coordinator/test_coordinator_core.py +++ b/tests/coordinator/test_coordinator_core.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from types import SimpleNamespace -from AIDojoCoordinator.coordinator import GameCoordinator +from AIDojoCoordinator.coordinator.coordinator import GameCoordinator from AIDojoCoordinator.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus # ----------------------- diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index e47eb233..d60659b1 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -1,6 +1,6 @@ import pytest from AIDojoCoordinator.game_components import ActionType, Action -from AIDojoCoordinator.global_defender import GlobalDefender +from AIDojoCoordinator.coordinator.global_defender import GlobalDefender from unittest.mock import patch @pytest.fixture From dd9c862136826f4ddf2d3d69bb7ec701b6bbfda8 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 16:58:55 +0100 Subject: [PATCH 002/112] Add configuration file for NetSecGame environment --- .../{ => coordinator}/netsecevn_conf_cyst_integration.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename AIDojoCoordinator/{ => coordinator}/netsecevn_conf_cyst_integration.yaml (100%) diff --git a/AIDojoCoordinator/netsecevn_conf_cyst_integration.yaml b/AIDojoCoordinator/coordinator/netsecevn_conf_cyst_integration.yaml similarity index 100% rename from AIDojoCoordinator/netsecevn_conf_cyst_integration.yaml rename to AIDojoCoordinator/coordinator/netsecevn_conf_cyst_integration.yaml From 17d855114bfd1d0812ed2524a78f9e116bd94c42 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:07:44 +0100 Subject: [PATCH 003/112] Add ConfigParser class for configuration management in NetSecGame --- .../coordinator/config_parser.py | 425 ++++++++++++++++++ AIDojoCoordinator/coordinator/coordinator.py | 3 +- AIDojoCoordinator/utils/utils.py | 406 ----------------- 3 files changed, 427 insertions(+), 407 deletions(-) create mode 100644 AIDojoCoordinator/coordinator/config_parser.py diff --git a/AIDojoCoordinator/coordinator/config_parser.py b/AIDojoCoordinator/coordinator/config_parser.py new file mode 100644 index 00000000..125aacd7 --- /dev/null +++ b/AIDojoCoordinator/coordinator/config_parser.py @@ -0,0 +1,425 @@ +# Config parser for NetSecGame Coordinator +# Author: Sebastian Garcia. sebastian.garcia@agents.fel.cvut.cz +# Author: Ondrej Lukas, ondrej.lukas@aic.fel.cvut.cz + +import yaml +# This is used so the agent can see the environment and game components +import importlib +from AIDojoCoordinator.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType +import netaddr +import logging +import os +import jsonlines +from random import randint +from cyst.api.configuration.network.node import NodeConfig +from typing import Optional +from AIDojoCoordinator.utils.utils import get_file_hash, get_str_hash + +class ConfigParser(): + """ + Class to deal with the configuration file of NetSecGame Coordinator + Args: + task_config_file (str|None): Path to the configuration file + config_dict (dict|None): Dictionary with configuration data + """ + def __init__(self, task_config_file:str|None=None, config_dict:dict|None=None): + """ + Initializes the configuration parser. Required either path to a confgiuration file or a dict with configuraitons. + """ + self.logger = logging.getLogger('configparser') + if task_config_file: + self.read_config_file(task_config_file) + elif config_dict: + self.config = config_dict + else: + self.logger.error("You must provide either the configuration file or a dictionary with the configuration!") + + def read_config_file(self, conf_file_name:str): + """ + reads configuration file + """ + try: + with open(conf_file_name) as source: + self.config = yaml.safe_load(source) + except (IOError, TypeError) as e: + self.logger.error(f'Error loading the configuration file{e}') + pass + + def read_env_action_data(self, action_name: str) -> float: + """ + Generic function to read the known data for any agent and goal of position + """ + try: + action_success_p = self.config['env']['actions'][action_name]['prob_success'] + except KeyError: + action_success_p = 1 + return action_success_p + + def read_agents_known_data(self, type_agent: str, type_data: str) -> dict: + """ + Generic function to read the known data for any agent and goal of position + """ + known_data_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_data'] + known_data = {} + for ip, data in known_data_conf.items(): + try: + # Check the host is a good ip + _ = netaddr.IPAddress(ip) + known_data_host = IP(ip) + known_data[known_data_host] = set() + for datum in data: + if not isinstance(datum, list) and datum.lower() == "random": + known_data[known_data_host].add("random") + else: + known_data_content_str_user = datum[0] + known_data_content_str_data = datum[1] + known_data_content = Data(known_data_content_str_user, known_data_content_str_data) + known_data[known_data_host].add(known_data_content) + + except (ValueError, netaddr.AddrFormatError): + known_data = {} + return known_data + + def read_agents_known_blocks(self, type_agent: str, type_data: str) -> dict: + """ + Generic function to read the known blocks for any agent and goal of position + """ + known_blocks_conf = self.config["coordinator"]['agents'][type_agent][type_data]['known_blocks'] + known_blocks = {} + for target_host, block_list in known_blocks_conf.items(): + try: + target_host = IP(target_host) + except ValueError: + self.logger.error(f"Error when converting {target_host} to IP address object") + if isinstance(block_list,list): + known_blocks[target_host] = map(lambda x: IP(x), block_list) + elif block_list == "all_attackers": + known_blocks[target_host] = block_list + else: + raise ValueError(f"Unsupported value in 'known_blocks': {known_blocks_conf}") + return known_blocks + + def read_agents_known_services(self, type_agent: str, type_data: str) -> dict: + """ + Generic function to read the known services for any agent and goal of position + """ + known_services_conf = self.config["coordinator"]['agents'][type_agent][type_data]['known_services'] + known_services = {} + for ip, data in known_services_conf.items(): + try: + # Check the host is a good ip + _ = netaddr.IPAddress(ip) + known_services_host = IP(ip) + known_services[known_services_host] = [] + for service in data: # process each item in the list + if isinstance(service, list): # Service defined as list + name = service[0] + type = service[1] + version = service[2] + is_local = service[3] + known_services[known_services_host].append(Service(name, type, version, is_local)) + elif isinstance(service, str): # keyword + if service.lower() == "random": + known_services[known_services_host].append("random") + else: + logging.warning(f"Unsupported values in agent known_services{ip}:{service}") + else: + logging.warning(f"Unsupported values in agent known_services{ip}:{service}") + except (ValueError, netaddr.AddrFormatError): + known_services = {} + return known_services + + def read_agents_known_networks(self, type_agent: str, type_data: str) -> set: + """ + Generic function to read the known networks for any agent and goal of position + """ + known_networks_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_networks'] + known_networks = set() + for net in known_networks_conf: + try: + if '/' in net: + _ = netaddr.IPNetwork(net) + host_part, net_part = net.split('/') + known_networks.add(Network(host_part, int(net_part))) + except (ValueError, TypeError, netaddr.AddrFormatError): + self.logger.error('Configuration problem with the known networks') + return known_networks + + def read_agents_known_hosts(self, type_agent: str, type_data: str) -> set: + """ + Generic function to read the known hosts for any agent and goal of position + """ + known_hosts_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_hosts'] + known_hosts = set() + for ip in known_hosts_conf: + try: + _ = netaddr.IPAddress(ip) + known_hosts.add(IP(ip)) + except (ValueError, netaddr.AddrFormatError) as e : + if ip == 'random': + # A random start ip was asked for + known_hosts.add('random') + elif ip == 'all_local': + known_hosts.add('all_local') + else: + self.logger.error(f'Configuration problem with the known hosts: {e}') + return known_hosts + + def read_agents_controlled_hosts(self, type_agent: str, type_data: str) -> set: + """ + Generic function to read the controlled hosts for any agent and goal of position + """ + controlled_hosts_conf = self.config['coordinator']['agents'][type_agent][type_data]['controlled_hosts'] + controlled_hosts = set() + for ip in controlled_hosts_conf: + try: + _ = netaddr.IPAddress(ip) + controlled_hosts.add(IP(ip)) + except (ValueError, netaddr.AddrFormatError) as e: + if ip == 'random' : + # A random start ip was asked for + controlled_hosts.add('random') + elif ip == 'all_local': + controlled_hosts.add('all_local') + else: + self.logger.error(f'Configuration problem with the controlled hosts: {e}') + return controlled_hosts + + def get_player_win_conditions(self, type_of_player:str): + """ + Get the goal of the player + type_of_player: Can be 'attackers' or 'defenders' + """ + # Read known nets + known_networks = self.read_agents_known_networks(type_of_player, 'goal') + + # Read known hosts + known_hosts = self.read_agents_known_hosts(type_of_player, 'goal') + + # Read controlled hosts + controlled_hosts = self.read_agents_controlled_hosts(type_of_player, 'goal') + + # Goal services + known_services = self.read_agents_known_services(type_of_player, 'goal') + + # Read known blocks + known_blocks = self.read_agents_known_blocks(type_of_player, 'goal') + + # Goal data + known_data = self.read_agents_known_data(type_of_player, 'goal') + + # Blocks + known_blocks = self.read_agents_known_blocks(type_of_player, 'goal') + + player_goal = {} + player_goal['known_networks'] = known_networks + player_goal['controlled_hosts'] = controlled_hosts + player_goal['known_hosts'] = known_hosts + player_goal['known_data'] = known_data + player_goal['known_services'] = known_services + player_goal['known_blocks'] = known_blocks + + return player_goal + + def get_player_start_position(self, type_of_player:str): + """ + Generate the starting position of an attacking agent + type_of_player: Can be 'attackers' or 'defenders' + """ + # Read known nets + known_networks = self.read_agents_known_networks(type_of_player, 'start_position') + + # Read known hosts + known_hosts = self.read_agents_known_hosts(type_of_player, 'start_position') + + # Read controlled hosts + controlled_hosts = self.read_agents_controlled_hosts(type_of_player, 'start_position') + + # Start services + known_services = self.read_agents_known_services(type_of_player, 'start_position') + + # Start data + known_data = self.read_agents_known_data(type_of_player, 'start_position') + + player_start_position = {} + player_start_position['known_networks'] = known_networks + player_start_position['controlled_hosts'] = controlled_hosts + player_start_position['known_hosts'] = known_hosts + player_start_position['known_data'] = known_data + player_start_position['known_services'] = known_services + + return player_start_position + + def get_start_position(self, agent_role:str): + match agent_role: + case "Attacker": + return self.get_player_start_position(agent_role) + case "Defender": + return self.get_player_start_position(agent_role) + case "Benign": + return { + 'known_networks': set(), + 'controlled_hosts': ["random", "random", "random"], + 'known_hosts': set(), + 'known_data': {}, + 'known_services': {} + } + case _: + raise ValueError(f"Unsupported agent role: {agent_role}") + + def get_win_conditions(self, agent_role): + match agent_role: + case "Attacker": + return self.get_player_win_conditions(agent_role) + case "Defender": + return self.get_player_win_conditions(agent_role) + case "Benign": + # create goal that is unreachable so we have infinite play by the benign agent + return { + 'known_networks': set(), + 'controlled_hosts': set(), + 'known_hosts': set(), + 'known_data': {IP("1.1.1.1"): {Data(owner='User1', id='DataFromInternet', size=0, type='')}}, + 'known_services': {}, + 'known_blocks': {} + } + case _: + raise ValueError(f"Unsupported agent role: {agent_role}") + + def get_max_steps(self, role=str)->Optional[int]: + """ + Get the max steps based on agent's role + """ + try: + max_steps = int(self.config['coordinator']['agents'][role]["max_steps"]) + except KeyError: + max_steps = None + self.logger.warning(f"Item 'max_steps' not found in 'coordinator.agents.{role}'!. Setting value to default=None (no step limit)") + except TypeError as e: + max_steps = None + self.logger.warning(f"Unsupported value in 'coordinator.agents.{role}.max_steps': {e}. Setting value to default=None (no step limit)") + return max_steps + + def get_goal_description(self, agent_role)->str: + """ + Get goal description per role + """ + match agent_role: + case "Attacker": + try: + description = self.config['coordinator']['agents'][agent_role]["goal"]["description"] + except KeyError: + description = "" + case "Defender": + try: + description = self.config['coordinator']['agents'][agent_role]["goal"]["description"] + except KeyError: + description = "" + case "Benign": + description = "" + case _: + raise ValueError(f"Unsupported agent role: {agent_role}") + return description + + def get_rewards(self, reward_names:list, default_value=0)->dict: + "Reads configuration for rewards for cases listed in 'rewards_names'" + rewards = {} + for name in reward_names: + try: + rewards[name] = self.config['env']["rewards"][name] + except KeyError: + self.logger.warning(f"No reward value found for '{name}'. Usinng default reward({name})={default_value}") + rewards[name] = default_value + return rewards + + def get_use_dynamic_addresses(self)->bool: + """ + Reads if the IP and Network addresses should be dynamically changed. + """ + try: + use_dynamic_addresses = self.config['env']['use_dynamic_addresses'] + except KeyError: + use_dynamic_addresses = False + return bool(use_dynamic_addresses) + + def get_store_trajectories(self): + """ + Read if the replay buffer should be stored in file + """ + try: + store_rb = self.config['env']['save_trajectories'] + except KeyError: + # Option is not in the configuration - default to FALSE + store_rb = False + return store_rb + + def get_scenario(self): + """ + Get the scenario config objects based on the configuration. Only import objects that are selected via importlib. + """ + allowed_names = { + "scenario1" : "AIDojoCoordinator.scenarios.scenario_configuration", + "scenario1_small" : "AIDojoCoordinator.scenarios.smaller_scenario_configuration", + "scenario1_tiny" : "AIDojoCoordinator.scenarios.tiny_scenario_configuration", + "one_network": "AIDojoCoordinator.scenarios.one_net", + "three_net_scenario": "AIDojoCoordinator.scenarios.three_net_scenario", + "two_networks": "AIDojoCoordinator.scenarios.two_nets", # same as scenario1 + "two_networks_small": "AIDojoCoordinator.scenarios.two_nets_small", # same as scenario1_small + "two_networks_tiny": "AIDojoCoordinator.scenarios.two_nets_tiny", # same as scenario1_small + + } + scenario_name = self.config['env']['scenario'] + # make sure to validate the input + if scenario_name not in allowed_names: + raise ValueError(f"Unsupported scenario: {scenario_name}") + + # import the correct module + module = importlib.import_module(allowed_names[scenario_name]) + return module.configuration_objects + + def get_seed(self, whom): + """ + Get the seeds + """ + seed = self.config[whom]['random_seed'] + if seed == 'random': + seed = randint(0,100) + return seed + + def get_randomize_goal_every_episode(self) -> bool: + """ + Get if the randomization should be done only once or at the beginning of every episode + """ + try: + randomize_goal_every_episode = self.config["coordinator"]["agents"]["attackers"]["goal"]["is_any_part_of_goal_random"] + except KeyError: + # Option is not in the configuration - default to FALSE + randomize_goal_every_episode = False + return randomize_goal_every_episode + + def get_use_firewall(self)->bool: + """ + Retrieves if the firewall functionality is allowed for netsecgame. + Default: False + """ + try: + use_firewall = self.config['env']['use_firewall'] + except KeyError: + use_firewall = False + return use_firewall + + def get_use_global_defender(self)->bool: + try: + use_global_defender = self.config['env']['use_global_defender'] + except KeyError: + use_global_defender = False + return use_global_defender + + def get_required_num_players(self)->int: + try: + required_players = int(self.config['env']['required_players']) + except KeyError: + required_players = 1 + except ValueError: + required_players = 1 + return required_players \ No newline at end of file diff --git a/AIDojoCoordinator/coordinator/coordinator.py b/AIDojoCoordinator/coordinator/coordinator.py index 5075cb8f..938e214b 100644 --- a/AIDojoCoordinator/coordinator/coordinator.py +++ b/AIDojoCoordinator/coordinator/coordinator.py @@ -6,7 +6,8 @@ from AIDojoCoordinator.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig from AIDojoCoordinator.coordinator.global_defender import GlobalDefender -from AIDojoCoordinator.utils.utils import observation_as_dict, get_str_hash, ConfigParser, store_trajectories_to_jsonl +from AIDojoCoordinator.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl +from AIDojoCoordinator.coordinator.config_parser import ConfigParser import os from aiohttp import ClientSession from cyst.api.environment.environment import Environment diff --git a/AIDojoCoordinator/utils/utils.py b/AIDojoCoordinator/utils/utils.py index face37cf..182e2a55 100644 --- a/AIDojoCoordinator/utils/utils.py +++ b/AIDojoCoordinator/utils/utils.py @@ -130,412 +130,6 @@ def parse_log_content(log_content:str)->Optional[list]: print(f"Error decoding JSON: {e}") return None -class ConfigParser(): - """ - Class to deal with the configuration file - """ - def __init__(self, task_config_file:str=None, config_dict:dict=None): - """ - Initializes the configuration parser. Required either path to a confgiuration file or a dict with configuraitons. - """ - self.logger = logging.getLogger('configparser') - if task_config_file: - self.read_config_file(task_config_file) - elif config_dict: - self.config = config_dict - else: - self.logger.error("You must provide either the configuration file or a dictionary with the configuration!") - - def read_config_file(self, conf_file_name:str): - """ - reads configuration file - """ - try: - with open(conf_file_name) as source: - self.config = yaml.safe_load(source) - except (IOError, TypeError) as e: - self.logger.error(f'Error loading the configuration file{e}') - pass - - def read_env_action_data(self, action_name: str) -> float: - """ - Generic function to read the known data for any agent and goal of position - """ - try: - action_success_p = self.config['env']['actions'][action_name]['prob_success'] - except KeyError: - action_success_p = 1 - return action_success_p - - def read_agents_known_data(self, type_agent: str, type_data: str) -> dict: - """ - Generic function to read the known data for any agent and goal of position - """ - known_data_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_data'] - known_data = {} - for ip, data in known_data_conf.items(): - try: - # Check the host is a good ip - _ = netaddr.IPAddress(ip) - known_data_host = IP(ip) - known_data[known_data_host] = set() - for datum in data: - if not isinstance(datum, list) and datum.lower() == "random": - known_data[known_data_host].add("random") - else: - known_data_content_str_user = datum[0] - known_data_content_str_data = datum[1] - known_data_content = Data(known_data_content_str_user, known_data_content_str_data) - known_data[known_data_host].add(known_data_content) - - except (ValueError, netaddr.AddrFormatError): - known_data = {} - return known_data - - def read_agents_known_blocks(self, type_agent: str, type_data: str) -> dict: - """ - Generic function to read the known blocks for any agent and goal of position - """ - known_blocks_conf = self.config["coordinator"]['agents'][type_agent][type_data]['known_blocks'] - known_blocks = {} - for target_host, block_list in known_blocks_conf.items(): - try: - target_host = IP(target_host) - except ValueError: - self.logger.error(f"Error when converting {target_host} to IP address object") - if isinstance(block_list,list): - known_blocks[target_host] = map(lambda x: IP(x), block_list) - elif block_list == "all_attackers": - known_blocks[target_host] = block_list - else: - raise ValueError(f"Unsupported value in 'known_blocks': {known_blocks_conf}") - return known_blocks - - def read_agents_known_services(self, type_agent: str, type_data: str) -> dict: - """ - Generic function to read the known services for any agent and goal of position - """ - known_services_conf = self.config["coordinator"]['agents'][type_agent][type_data]['known_services'] - known_services = {} - for ip, data in known_services_conf.items(): - try: - # Check the host is a good ip - _ = netaddr.IPAddress(ip) - known_services_host = IP(ip) - known_services[known_services_host] = [] - for service in data: # process each item in the list - if isinstance(service, list): # Service defined as list - name = service[0] - type = service[1] - version = service[2] - is_local = service[3] - known_services[known_services_host].append(Service(name, type, version, is_local)) - elif isinstance(service, str): # keyword - if service.lower() == "random": - known_services[known_services_host].append("random") - else: - logging.warning(f"Unsupported values in agent known_services{ip}:{service}") - else: - logging.warning(f"Unsupported values in agent known_services{ip}:{service}") - except (ValueError, netaddr.AddrFormatError): - known_services = {} - return known_services - - def read_agents_known_networks(self, type_agent: str, type_data: str) -> set: - """ - Generic function to read the known networks for any agent and goal of position - """ - known_networks_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_networks'] - known_networks = set() - for net in known_networks_conf: - try: - if '/' in net: - _ = netaddr.IPNetwork(net) - host_part, net_part = net.split('/') - known_networks.add(Network(host_part, int(net_part))) - except (ValueError, TypeError, netaddr.AddrFormatError): - self.logger.error('Configuration problem with the known networks') - return known_networks - - def read_agents_known_hosts(self, type_agent: str, type_data: str) -> set: - """ - Generic function to read the known hosts for any agent and goal of position - """ - known_hosts_conf = self.config['coordinator']['agents'][type_agent][type_data]['known_hosts'] - known_hosts = set() - for ip in known_hosts_conf: - try: - _ = netaddr.IPAddress(ip) - known_hosts.add(IP(ip)) - except (ValueError, netaddr.AddrFormatError) as e : - if ip == 'random': - # A random start ip was asked for - known_hosts.add('random') - elif ip == 'all_local': - known_hosts.add('all_local') - else: - self.logger.error(f'Configuration problem with the known hosts: {e}') - return known_hosts - - def read_agents_controlled_hosts(self, type_agent: str, type_data: str) -> set: - """ - Generic function to read the controlled hosts for any agent and goal of position - """ - controlled_hosts_conf = self.config['coordinator']['agents'][type_agent][type_data]['controlled_hosts'] - controlled_hosts = set() - for ip in controlled_hosts_conf: - try: - _ = netaddr.IPAddress(ip) - controlled_hosts.add(IP(ip)) - except (ValueError, netaddr.AddrFormatError) as e: - if ip == 'random' : - # A random start ip was asked for - controlled_hosts.add('random') - elif ip == 'all_local': - controlled_hosts.add('all_local') - else: - self.logger.error(f'Configuration problem with the controlled hosts: {e}') - return controlled_hosts - - def get_player_win_conditions(self, type_of_player:str): - """ - Get the goal of the player - type_of_player: Can be 'attackers' or 'defenders' - """ - # Read known nets - known_networks = self.read_agents_known_networks(type_of_player, 'goal') - - # Read known hosts - known_hosts = self.read_agents_known_hosts(type_of_player, 'goal') - - # Read controlled hosts - controlled_hosts = self.read_agents_controlled_hosts(type_of_player, 'goal') - - # Goal services - known_services = self.read_agents_known_services(type_of_player, 'goal') - - # Read known blocks - known_blocks = self.read_agents_known_blocks(type_of_player, 'goal') - - # Goal data - known_data = self.read_agents_known_data(type_of_player, 'goal') - - # Blocks - known_blocks = self.read_agents_known_blocks(type_of_player, 'goal') - - player_goal = {} - player_goal['known_networks'] = known_networks - player_goal['controlled_hosts'] = controlled_hosts - player_goal['known_hosts'] = known_hosts - player_goal['known_data'] = known_data - player_goal['known_services'] = known_services - player_goal['known_blocks'] = known_blocks - - return player_goal - - def get_player_start_position(self, type_of_player:str): - """ - Generate the starting position of an attacking agent - type_of_player: Can be 'attackers' or 'defenders' - """ - # Read known nets - known_networks = self.read_agents_known_networks(type_of_player, 'start_position') - - # Read known hosts - known_hosts = self.read_agents_known_hosts(type_of_player, 'start_position') - - # Read controlled hosts - controlled_hosts = self.read_agents_controlled_hosts(type_of_player, 'start_position') - - # Start services - known_services = self.read_agents_known_services(type_of_player, 'start_position') - - # Start data - known_data = self.read_agents_known_data(type_of_player, 'start_position') - - player_start_position = {} - player_start_position['known_networks'] = known_networks - player_start_position['controlled_hosts'] = controlled_hosts - player_start_position['known_hosts'] = known_hosts - player_start_position['known_data'] = known_data - player_start_position['known_services'] = known_services - - return player_start_position - - def get_start_position(self, agent_role:str): - match agent_role: - case "Attacker": - return self.get_player_start_position(agent_role) - case "Defender": - return self.get_player_start_position(agent_role) - case "Benign": - return { - 'known_networks': set(), - 'controlled_hosts': ["random", "random", "random"], - 'known_hosts': set(), - 'known_data': {}, - 'known_services': {} - } - case _: - raise ValueError(f"Unsupported agent role: {agent_role}") - - def get_win_conditions(self, agent_role): - match agent_role: - case "Attacker": - return self.get_player_win_conditions(agent_role) - case "Defender": - return self.get_player_win_conditions(agent_role) - case "Benign": - # create goal that is unreachable so we have infinite play by the benign agent - return { - 'known_networks': set(), - 'controlled_hosts': set(), - 'known_hosts': set(), - 'known_data': {IP("1.1.1.1"): {Data(owner='User1', id='DataFromInternet', size=0, type='')}}, - 'known_services': {}, - 'known_blocks': {} - } - case _: - raise ValueError(f"Unsupported agent role: {agent_role}") - - def get_max_steps(self, role=str)->Optional[int]: - """ - Get the max steps based on agent's role - """ - try: - max_steps = int(self.config['coordinator']['agents'][role]["max_steps"]) - except KeyError: - max_steps = None - self.logger.warning(f"Item 'max_steps' not found in 'coordinator.agents.{role}'!. Setting value to default=None (no step limit)") - except TypeError as e: - max_steps = None - self.logger.warning(f"Unsupported value in 'coordinator.agents.{role}.max_steps': {e}. Setting value to default=None (no step limit)") - return max_steps - - def get_goal_description(self, agent_role)->str: - """ - Get goal description per role - """ - match agent_role: - case "Attacker": - try: - description = self.config['coordinator']['agents'][agent_role]["goal"]["description"] - except KeyError: - description = "" - case "Defender": - try: - description = self.config['coordinator']['agents'][agent_role]["goal"]["description"] - except KeyError: - description = "" - case "Benign": - description = "" - case _: - raise ValueError(f"Unsupported agent role: {agent_role}") - return description - - def get_rewards(self, reward_names:list, default_value=0)->dict: - "Reads configuration for rewards for cases listed in 'rewards_names'" - rewards = {} - for name in reward_names: - try: - rewards[name] = self.config['env']["rewards"][name] - except KeyError: - self.logger.warning(f"No reward value found for '{name}'. Usinng default reward({name})={default_value}") - rewards[name] = default_value - return rewards - - def get_use_dynamic_addresses(self)->bool: - """ - Reads if the IP and Network addresses should be dynamically changed. - """ - try: - use_dynamic_addresses = self.config['env']['use_dynamic_addresses'] - except KeyError: - use_dynamic_addresses = False - return bool(use_dynamic_addresses) - - def get_store_trajectories(self): - """ - Read if the replay buffer should be stored in file - """ - try: - store_rb = self.config['env']['save_trajectories'] - except KeyError: - # Option is not in the configuration - default to FALSE - store_rb = False - return store_rb - - def get_scenario(self): - """ - Get the scenario config objects based on the configuration. Only import objects that are selected via importlib. - """ - allowed_names = { - "scenario1" : "AIDojoCoordinator.scenarios.scenario_configuration", - "scenario1_small" : "AIDojoCoordinator.scenarios.smaller_scenario_configuration", - "scenario1_tiny" : "AIDojoCoordinator.scenarios.tiny_scenario_configuration", - "one_network": "AIDojoCoordinator.scenarios.one_net", - "three_net_scenario": "AIDojoCoordinator.scenarios.three_net_scenario", - "two_networks": "AIDojoCoordinator.scenarios.two_nets", # same as scenario1 - "two_networks_small": "AIDojoCoordinator.scenarios.two_nets_small", # same as scenario1_small - "two_networks_tiny": "AIDojoCoordinator.scenarios.two_nets_tiny", # same as scenario1_small - - } - scenario_name = self.config['env']['scenario'] - # make sure to validate the input - if scenario_name not in allowed_names: - raise ValueError(f"Unsupported scenario: {scenario_name}") - - # import the correct module - module = importlib.import_module(allowed_names[scenario_name]) - return module.configuration_objects - - def get_seed(self, whom): - """ - Get the seeds - """ - seed = self.config[whom]['random_seed'] - if seed == 'random': - seed = randint(0,100) - return seed - - def get_randomize_goal_every_episode(self) -> bool: - """ - Get if the randomization should be done only once or at the beginning of every episode - """ - try: - randomize_goal_every_episode = self.config["coordinator"]["agents"]["attackers"]["goal"]["is_any_part_of_goal_random"] - except KeyError: - # Option is not in the configuration - default to FALSE - randomize_goal_every_episode = False - return randomize_goal_every_episode - - def get_use_firewall(self)->bool: - """ - Retrieves if the firewall functionality is allowed for netsecgame. - Default: False - """ - try: - use_firewall = self.config['env']['use_firewall'] - except KeyError: - use_firewall = False - return use_firewall - - def get_use_global_defender(self)->bool: - try: - use_global_defender = self.config['env']['use_global_defender'] - except KeyError: - use_global_defender = False - return use_global_defender - - def get_required_num_players(self)->int: - try: - required_players = int(self.config['env']['required_players']) - except KeyError: - required_players = 1 - except ValueError: - required_players = 1 - return required_players - def get_logging_level(debug_level): """ Configure logging level based on the provided debug_level string. From db99b1efcec8abde078c4875c32caf2893ea123b Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:11:28 +0100 Subject: [PATCH 004/112] Refactor imports in ConfigParser to streamline dependencies --- AIDojoCoordinator/coordinator/config_parser.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/AIDojoCoordinator/coordinator/config_parser.py b/AIDojoCoordinator/coordinator/config_parser.py index 125aacd7..36e86805 100644 --- a/AIDojoCoordinator/coordinator/config_parser.py +++ b/AIDojoCoordinator/coordinator/config_parser.py @@ -5,15 +5,11 @@ import yaml # This is used so the agent can see the environment and game components import importlib -from AIDojoCoordinator.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType +from AIDojoCoordinator.game_components import IP, Data, Network, Service import netaddr import logging -import os -import jsonlines from random import randint -from cyst.api.configuration.network.node import NodeConfig from typing import Optional -from AIDojoCoordinator.utils.utils import get_file_hash, get_str_hash class ConfigParser(): """ From f05db09198bd4a7eb3119373e9ec6898805e305d Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:12:44 +0100 Subject: [PATCH 005/112] Rename to NetSecGame --- {AIDojoCoordinator => NetSecGame}/__init__.py | 0 .../coordinator/__init__.py | 0 .../coordinator/config_parser.py | 2 +- .../coordinator/coordinator.py | 8 ++++---- .../coordinator/global_defender.py | 2 +- .../coordinator/netsecenv_conf.yaml | 0 .../netsecevn_conf_cyst_integration.yaml | 0 .../coordinator/worlds/CYSTCoordinator.py | 6 +++--- .../coordinator/worlds/NSEGameCoordinator.py | 6 +++--- .../coordinator/worlds/NSGRealWorldCoordinator.py | 6 +++--- .../coordinator/worlds/WhiteBoxNSGCoordinator.py | 6 +++--- .../coordinator/worlds/__init__.py | 0 .../docs/Components.md | 0 .../docs/Coordinator.md | 0 .../docs/Trajectory_analysis.md | 0 .../docs/figures/architecture_diagram.jpg | Bin .../docs/figures/message_passing_coordinator.jpg | Bin .../docs/figures/scenarios/scenario 1_small.png | Bin .../docs/figures/scenarios/scenario_1.png | Bin .../docs/figures/scenarios/scenario_1_tiny.png | Bin .../docs/figures/scenarios/three_nets.png | Bin .../game_components.py | 0 .../scenarios/__init__.py | 0 .../scenarios/one_net.py | 0 .../scenarios/scenario_configuration.py | 0 .../scenarios/smaller_scenario_configuration.py | 0 .../scenarios/test_scenario_configuration.py | 0 .../scenarios/three_net_scenario.py | 0 .../scenarios/tiny_scenario_configuration.py | 0 .../scenarios/two_nets.py | 0 .../scenarios/two_nets_small.py | 0 .../scenarios/two_nets_tiny.py | 0 {AIDojoCoordinator => NetSecGame}/utils/__init__.py | 0 .../utils/action_plots.r | 0 .../utils/action_plots_readme.md | 0 .../utils/actions_parser.py | 0 .../utils/aidojo_log_colorizer.py | 0 .../utils/gamaplay_graphs.py | 2 +- .../utils/log_parser.py | 0 .../utils/trajectory_analysis.py | 2 +- {AIDojoCoordinator => NetSecGame}/utils/utils.py | 2 +- tests/OLD_test_actions.py | 4 ++-- tests/components/test_action.py | 2 +- tests/components/test_data.py | 2 +- tests/components/test_game_state.py | 2 +- tests/components/test_ip.py | 2 +- tests/components/test_network.py | 2 +- tests/components/test_service.py | 2 +- tests/coordinator/test_agent_server.py | 4 ++-- tests/coordinator/test_coordinator_core.py | 4 ++-- tests/coordinator/test_global_defender.py | 4 ++-- .../three_nets/manual_test_three_net_scenario.py | 2 +- 52 files changed, 36 insertions(+), 36 deletions(-) rename {AIDojoCoordinator => NetSecGame}/__init__.py (100%) rename {AIDojoCoordinator => NetSecGame}/coordinator/__init__.py (100%) rename {AIDojoCoordinator => NetSecGame}/coordinator/config_parser.py (99%) rename {AIDojoCoordinator => NetSecGame}/coordinator/coordinator.py (99%) rename {AIDojoCoordinator => NetSecGame}/coordinator/global_defender.py (98%) rename {AIDojoCoordinator => NetSecGame}/coordinator/netsecenv_conf.yaml (100%) rename {AIDojoCoordinator => NetSecGame}/coordinator/netsecevn_conf_cyst_integration.yaml (100%) rename {AIDojoCoordinator => NetSecGame}/coordinator/worlds/CYSTCoordinator.py (97%) rename {AIDojoCoordinator => NetSecGame}/coordinator/worlds/NSEGameCoordinator.py (99%) rename {AIDojoCoordinator => NetSecGame}/coordinator/worlds/NSGRealWorldCoordinator.py (97%) rename {AIDojoCoordinator => NetSecGame}/coordinator/worlds/WhiteBoxNSGCoordinator.py (97%) rename {AIDojoCoordinator => NetSecGame}/coordinator/worlds/__init__.py (100%) rename {AIDojoCoordinator => NetSecGame}/docs/Components.md (100%) rename {AIDojoCoordinator => NetSecGame}/docs/Coordinator.md (100%) rename {AIDojoCoordinator => NetSecGame}/docs/Trajectory_analysis.md (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/architecture_diagram.jpg (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/message_passing_coordinator.jpg (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/scenarios/scenario 1_small.png (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/scenarios/scenario_1.png (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/scenarios/scenario_1_tiny.png (100%) rename {AIDojoCoordinator => NetSecGame}/docs/figures/scenarios/three_nets.png (100%) rename {AIDojoCoordinator => NetSecGame}/game_components.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/__init__.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/one_net.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/scenario_configuration.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/smaller_scenario_configuration.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/test_scenario_configuration.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/three_net_scenario.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/tiny_scenario_configuration.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/two_nets.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/two_nets_small.py (100%) rename {AIDojoCoordinator => NetSecGame}/scenarios/two_nets_tiny.py (100%) rename {AIDojoCoordinator => NetSecGame}/utils/__init__.py (100%) rename {AIDojoCoordinator => NetSecGame}/utils/action_plots.r (100%) rename {AIDojoCoordinator => NetSecGame}/utils/action_plots_readme.md (100%) rename {AIDojoCoordinator => NetSecGame}/utils/actions_parser.py (100%) rename {AIDojoCoordinator => NetSecGame}/utils/aidojo_log_colorizer.py (100%) rename {AIDojoCoordinator => NetSecGame}/utils/gamaplay_graphs.py (99%) rename {AIDojoCoordinator => NetSecGame}/utils/log_parser.py (100%) rename {AIDojoCoordinator => NetSecGame}/utils/trajectory_analysis.py (99%) rename {AIDojoCoordinator => NetSecGame}/utils/utils.py (98%) diff --git a/AIDojoCoordinator/__init__.py b/NetSecGame/__init__.py similarity index 100% rename from AIDojoCoordinator/__init__.py rename to NetSecGame/__init__.py diff --git a/AIDojoCoordinator/coordinator/__init__.py b/NetSecGame/coordinator/__init__.py similarity index 100% rename from AIDojoCoordinator/coordinator/__init__.py rename to NetSecGame/coordinator/__init__.py diff --git a/AIDojoCoordinator/coordinator/config_parser.py b/NetSecGame/coordinator/config_parser.py similarity index 99% rename from AIDojoCoordinator/coordinator/config_parser.py rename to NetSecGame/coordinator/config_parser.py index 36e86805..96dceed4 100644 --- a/AIDojoCoordinator/coordinator/config_parser.py +++ b/NetSecGame/coordinator/config_parser.py @@ -5,7 +5,7 @@ import yaml # This is used so the agent can see the environment and game components import importlib -from AIDojoCoordinator.game_components import IP, Data, Network, Service +from NetSecGame.game_components import IP, Data, Network, Service import netaddr import logging from random import randint diff --git a/AIDojoCoordinator/coordinator/coordinator.py b/NetSecGame/coordinator/coordinator.py similarity index 99% rename from AIDojoCoordinator/coordinator/coordinator.py rename to NetSecGame/coordinator/coordinator.py index 938e214b..74d13d24 100644 --- a/AIDojoCoordinator/coordinator/coordinator.py +++ b/NetSecGame/coordinator/coordinator.py @@ -4,10 +4,10 @@ from datetime import datetime import signal -from AIDojoCoordinator.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig -from AIDojoCoordinator.coordinator.global_defender import GlobalDefender -from AIDojoCoordinator.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl -from AIDojoCoordinator.coordinator.config_parser import ConfigParser +from NetSecGame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig +from NetSecGame.coordinator.global_defender import GlobalDefender +from NetSecGame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl +from NetSecGame.coordinator.config_parser import ConfigParser import os from aiohttp import ClientSession from cyst.api.environment.environment import Environment diff --git a/AIDojoCoordinator/coordinator/global_defender.py b/NetSecGame/coordinator/global_defender.py similarity index 98% rename from AIDojoCoordinator/coordinator/global_defender.py rename to NetSecGame/coordinator/global_defender.py index c784b11a..fcb497f0 100644 --- a/AIDojoCoordinator/coordinator/global_defender.py +++ b/NetSecGame/coordinator/global_defender.py @@ -1,6 +1,6 @@ # Author: Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz from itertools import groupby -from AIDojoCoordinator.game_components import ActionType, Action +from NetSecGame.game_components import ActionType, Action from random import random diff --git a/AIDojoCoordinator/coordinator/netsecenv_conf.yaml b/NetSecGame/coordinator/netsecenv_conf.yaml similarity index 100% rename from AIDojoCoordinator/coordinator/netsecenv_conf.yaml rename to NetSecGame/coordinator/netsecenv_conf.yaml diff --git a/AIDojoCoordinator/coordinator/netsecevn_conf_cyst_integration.yaml b/NetSecGame/coordinator/netsecevn_conf_cyst_integration.yaml similarity index 100% rename from AIDojoCoordinator/coordinator/netsecevn_conf_cyst_integration.yaml rename to NetSecGame/coordinator/netsecevn_conf_cyst_integration.yaml diff --git a/AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py b/NetSecGame/coordinator/worlds/CYSTCoordinator.py similarity index 97% rename from AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py rename to NetSecGame/coordinator/worlds/CYSTCoordinator.py index 6f65df73..0d34877d 100644 --- a/AIDojoCoordinator/coordinator/worlds/CYSTCoordinator.py +++ b/NetSecGame/coordinator/worlds/CYSTCoordinator.py @@ -8,10 +8,10 @@ import logging import argparse from pathlib import Path -from AIDojoCoordinator.game_components import GameState, Action, ActionType, IP, Service -from AIDojoCoordinator.coordinator.coordinator import GameCoordinator +from NetSecGame.game_components import GameState, Action, ActionType, IP, Service +from NetSecGame.coordinator.coordinator import GameCoordinator -from AIDojoCoordinator.utils.utils import get_starting_position_from_cyst_config, get_logging_level +from NetSecGame.utils.utils import get_starting_position_from_cyst_config, get_logging_level class CYSTCoordinator(GameCoordinator): diff --git a/AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py b/NetSecGame/coordinator/worlds/NSEGameCoordinator.py similarity index 99% rename from AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py rename to NetSecGame/coordinator/worlds/NSEGameCoordinator.py index f67bb4ed..36f09e4b 100644 --- a/AIDojoCoordinator/coordinator/worlds/NSEGameCoordinator.py +++ b/NetSecGame/coordinator/worlds/NSEGameCoordinator.py @@ -12,11 +12,11 @@ from typing import Iterable from collections import defaultdict -from AIDojoCoordinator.game_components import GameState, Action, ActionType, IP, Network, Data, Service -from AIDojoCoordinator.coordinator.coordinator import GameCoordinator +from NetSecGame.game_components import GameState, Action, ActionType, IP, Network, Data, Service +from NetSecGame.coordinator.coordinator import GameCoordinator from cyst.api.configuration import NodeConfig, RouterConfig, ConnectionConfig, ExploitConfig, FirewallPolicy -from AIDojoCoordinator.utils.utils import get_logging_level +from NetSecGame.utils.utils import get_logging_level class NSGCoordinator(GameCoordinator): diff --git a/AIDojoCoordinator/coordinator/worlds/NSGRealWorldCoordinator.py b/NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py similarity index 97% rename from AIDojoCoordinator/coordinator/worlds/NSGRealWorldCoordinator.py rename to NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py index 763326ab..543db5a2 100644 --- a/AIDojoCoordinator/coordinator/worlds/NSGRealWorldCoordinator.py +++ b/NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py @@ -9,9 +9,9 @@ import os from pathlib import Path -from AIDojoCoordinator.utils.utils import get_logging_level -from AIDojoCoordinator.game_components import GameState, Action, ActionType, Service,IP -from AIDojoCoordinator.worlds.NSEGameCoordinator import NSGCoordinator +from NetSecGame.utils.utils import get_logging_level +from NetSecGame.game_components import GameState, Action, ActionType, Service,IP +from NetSecGame.worlds.NSEGameCoordinator import NSGCoordinator class NSERealWorldGameCoordinator(NSGCoordinator): diff --git a/AIDojoCoordinator/coordinator/worlds/WhiteBoxNSGCoordinator.py b/NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py similarity index 97% rename from AIDojoCoordinator/coordinator/worlds/WhiteBoxNSGCoordinator.py rename to NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py index d9ac3285..7e89530a 100644 --- a/AIDojoCoordinator/coordinator/worlds/WhiteBoxNSGCoordinator.py +++ b/NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py @@ -4,9 +4,9 @@ import os import json from pathlib import Path -from AIDojoCoordinator.utils.utils import get_logging_level -from AIDojoCoordinator.game_components import Action, ActionType -from AIDojoCoordinator.worlds.NSEGameCoordinator import NSGCoordinator +from NetSecGame.utils.utils import get_logging_level +from NetSecGame.game_components import Action, ActionType +from NetSecGame.worlds.NSEGameCoordinator import NSGCoordinator diff --git a/AIDojoCoordinator/coordinator/worlds/__init__.py b/NetSecGame/coordinator/worlds/__init__.py similarity index 100% rename from AIDojoCoordinator/coordinator/worlds/__init__.py rename to NetSecGame/coordinator/worlds/__init__.py diff --git a/AIDojoCoordinator/docs/Components.md b/NetSecGame/docs/Components.md similarity index 100% rename from AIDojoCoordinator/docs/Components.md rename to NetSecGame/docs/Components.md diff --git a/AIDojoCoordinator/docs/Coordinator.md b/NetSecGame/docs/Coordinator.md similarity index 100% rename from AIDojoCoordinator/docs/Coordinator.md rename to NetSecGame/docs/Coordinator.md diff --git a/AIDojoCoordinator/docs/Trajectory_analysis.md b/NetSecGame/docs/Trajectory_analysis.md similarity index 100% rename from AIDojoCoordinator/docs/Trajectory_analysis.md rename to NetSecGame/docs/Trajectory_analysis.md diff --git a/AIDojoCoordinator/docs/figures/architecture_diagram.jpg b/NetSecGame/docs/figures/architecture_diagram.jpg similarity index 100% rename from AIDojoCoordinator/docs/figures/architecture_diagram.jpg rename to NetSecGame/docs/figures/architecture_diagram.jpg diff --git a/AIDojoCoordinator/docs/figures/message_passing_coordinator.jpg b/NetSecGame/docs/figures/message_passing_coordinator.jpg similarity index 100% rename from AIDojoCoordinator/docs/figures/message_passing_coordinator.jpg rename to NetSecGame/docs/figures/message_passing_coordinator.jpg diff --git a/AIDojoCoordinator/docs/figures/scenarios/scenario 1_small.png b/NetSecGame/docs/figures/scenarios/scenario 1_small.png similarity index 100% rename from AIDojoCoordinator/docs/figures/scenarios/scenario 1_small.png rename to NetSecGame/docs/figures/scenarios/scenario 1_small.png diff --git a/AIDojoCoordinator/docs/figures/scenarios/scenario_1.png b/NetSecGame/docs/figures/scenarios/scenario_1.png similarity index 100% rename from AIDojoCoordinator/docs/figures/scenarios/scenario_1.png rename to NetSecGame/docs/figures/scenarios/scenario_1.png diff --git a/AIDojoCoordinator/docs/figures/scenarios/scenario_1_tiny.png b/NetSecGame/docs/figures/scenarios/scenario_1_tiny.png similarity index 100% rename from AIDojoCoordinator/docs/figures/scenarios/scenario_1_tiny.png rename to NetSecGame/docs/figures/scenarios/scenario_1_tiny.png diff --git a/AIDojoCoordinator/docs/figures/scenarios/three_nets.png b/NetSecGame/docs/figures/scenarios/three_nets.png similarity index 100% rename from AIDojoCoordinator/docs/figures/scenarios/three_nets.png rename to NetSecGame/docs/figures/scenarios/three_nets.png diff --git a/AIDojoCoordinator/game_components.py b/NetSecGame/game_components.py similarity index 100% rename from AIDojoCoordinator/game_components.py rename to NetSecGame/game_components.py diff --git a/AIDojoCoordinator/scenarios/__init__.py b/NetSecGame/scenarios/__init__.py similarity index 100% rename from AIDojoCoordinator/scenarios/__init__.py rename to NetSecGame/scenarios/__init__.py diff --git a/AIDojoCoordinator/scenarios/one_net.py b/NetSecGame/scenarios/one_net.py similarity index 100% rename from AIDojoCoordinator/scenarios/one_net.py rename to NetSecGame/scenarios/one_net.py diff --git a/AIDojoCoordinator/scenarios/scenario_configuration.py b/NetSecGame/scenarios/scenario_configuration.py similarity index 100% rename from AIDojoCoordinator/scenarios/scenario_configuration.py rename to NetSecGame/scenarios/scenario_configuration.py diff --git a/AIDojoCoordinator/scenarios/smaller_scenario_configuration.py b/NetSecGame/scenarios/smaller_scenario_configuration.py similarity index 100% rename from AIDojoCoordinator/scenarios/smaller_scenario_configuration.py rename to NetSecGame/scenarios/smaller_scenario_configuration.py diff --git a/AIDojoCoordinator/scenarios/test_scenario_configuration.py b/NetSecGame/scenarios/test_scenario_configuration.py similarity index 100% rename from AIDojoCoordinator/scenarios/test_scenario_configuration.py rename to NetSecGame/scenarios/test_scenario_configuration.py diff --git a/AIDojoCoordinator/scenarios/three_net_scenario.py b/NetSecGame/scenarios/three_net_scenario.py similarity index 100% rename from AIDojoCoordinator/scenarios/three_net_scenario.py rename to NetSecGame/scenarios/three_net_scenario.py diff --git a/AIDojoCoordinator/scenarios/tiny_scenario_configuration.py b/NetSecGame/scenarios/tiny_scenario_configuration.py similarity index 100% rename from AIDojoCoordinator/scenarios/tiny_scenario_configuration.py rename to NetSecGame/scenarios/tiny_scenario_configuration.py diff --git a/AIDojoCoordinator/scenarios/two_nets.py b/NetSecGame/scenarios/two_nets.py similarity index 100% rename from AIDojoCoordinator/scenarios/two_nets.py rename to NetSecGame/scenarios/two_nets.py diff --git a/AIDojoCoordinator/scenarios/two_nets_small.py b/NetSecGame/scenarios/two_nets_small.py similarity index 100% rename from AIDojoCoordinator/scenarios/two_nets_small.py rename to NetSecGame/scenarios/two_nets_small.py diff --git a/AIDojoCoordinator/scenarios/two_nets_tiny.py b/NetSecGame/scenarios/two_nets_tiny.py similarity index 100% rename from AIDojoCoordinator/scenarios/two_nets_tiny.py rename to NetSecGame/scenarios/two_nets_tiny.py diff --git a/AIDojoCoordinator/utils/__init__.py b/NetSecGame/utils/__init__.py similarity index 100% rename from AIDojoCoordinator/utils/__init__.py rename to NetSecGame/utils/__init__.py diff --git a/AIDojoCoordinator/utils/action_plots.r b/NetSecGame/utils/action_plots.r similarity index 100% rename from AIDojoCoordinator/utils/action_plots.r rename to NetSecGame/utils/action_plots.r diff --git a/AIDojoCoordinator/utils/action_plots_readme.md b/NetSecGame/utils/action_plots_readme.md similarity index 100% rename from AIDojoCoordinator/utils/action_plots_readme.md rename to NetSecGame/utils/action_plots_readme.md diff --git a/AIDojoCoordinator/utils/actions_parser.py b/NetSecGame/utils/actions_parser.py similarity index 100% rename from AIDojoCoordinator/utils/actions_parser.py rename to NetSecGame/utils/actions_parser.py diff --git a/AIDojoCoordinator/utils/aidojo_log_colorizer.py b/NetSecGame/utils/aidojo_log_colorizer.py similarity index 100% rename from AIDojoCoordinator/utils/aidojo_log_colorizer.py rename to NetSecGame/utils/aidojo_log_colorizer.py diff --git a/AIDojoCoordinator/utils/gamaplay_graphs.py b/NetSecGame/utils/gamaplay_graphs.py similarity index 99% rename from AIDojoCoordinator/utils/gamaplay_graphs.py rename to NetSecGame/utils/gamaplay_graphs.py index a2f6f80e..13e71618 100644 --- a/AIDojoCoordinator/utils/gamaplay_graphs.py +++ b/NetSecGame/utils/gamaplay_graphs.py @@ -5,7 +5,7 @@ import argparse import matplotlib.pyplot as plt -from AIDojoCoordinator.game_components import GameState, Action +from NetSecGame.game_components import GameState, Action class TrajectoryGraph: def __init__(self)->None: diff --git a/AIDojoCoordinator/utils/log_parser.py b/NetSecGame/utils/log_parser.py similarity index 100% rename from AIDojoCoordinator/utils/log_parser.py rename to NetSecGame/utils/log_parser.py diff --git a/AIDojoCoordinator/utils/trajectory_analysis.py b/NetSecGame/utils/trajectory_analysis.py similarity index 99% rename from AIDojoCoordinator/utils/trajectory_analysis.py rename to NetSecGame/utils/trajectory_analysis.py index 1bed8d3d..99678aaf 100644 --- a/AIDojoCoordinator/utils/trajectory_analysis.py +++ b/NetSecGame/utils/trajectory_analysis.py @@ -9,7 +9,7 @@ import plotly.graph_objects as go from sklearn.preprocessing import StandardScaler -from AIDojoCoordinator.game_components import GameState, Action, ActionType +from NetSecGame.game_components import GameState, Action, ActionType diff --git a/AIDojoCoordinator/utils/utils.py b/NetSecGame/utils/utils.py similarity index 98% rename from AIDojoCoordinator/utils/utils.py rename to NetSecGame/utils/utils.py index 182e2a55..eaa089d3 100644 --- a/AIDojoCoordinator/utils/utils.py +++ b/NetSecGame/utils/utils.py @@ -5,7 +5,7 @@ import yaml # This is used so the agent can see the environment and game components import importlib -from AIDojoCoordinator.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType +from NetSecGame.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType import netaddr import logging import csv diff --git a/tests/OLD_test_actions.py b/tests/OLD_test_actions.py index 443c80bf..8b8434a9 100644 --- a/tests/OLD_test_actions.py +++ b/tests/OLD_test_actions.py @@ -5,8 +5,8 @@ import sys from os import path sys.path.append( path.dirname(path.dirname( path.abspath(__file__) ) )) -from AIDojoCoordinator.worlds.network_security_game import NetworkSecurityEnvironment -import AIDojoCoordinator.game_components as components +from NetSecGame.worlds.network_security_game import NetworkSecurityEnvironment +import NetSecGame.game_components as components import pytest # Fixture are used to hold the current state and the environment diff --git a/tests/components/test_action.py b/tests/components/test_action.py index b1c75526..f1af288a 100644 --- a/tests/components/test_action.py +++ b/tests/components/test_action.py @@ -1,7 +1,7 @@ # Authors: Maria Rigaki - maria.rigaki@aic.fel.cvut.cz # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import json -from AIDojoCoordinator.game_components import Action, ActionType, IP, Network, Data, Service, AgentInfo +from NetSecGame.game_components import Action, ActionType, IP, Network, Data, Service, AgentInfo class TestComponentActionType: """ diff --git a/tests/components/test_data.py b/tests/components/test_data.py index b3946f94..9882053a 100644 --- a/tests/components/test_data.py +++ b/tests/components/test_data.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from AIDojoCoordinator.game_components import Data +from NetSecGame.game_components import Data @pytest.fixture def sample_data_minimal(): diff --git a/tests/components/test_game_state.py b/tests/components/test_game_state.py index 770efb43..726fcc2c 100644 --- a/tests/components/test_game_state.py +++ b/tests/components/test_game_state.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import json import pytest -from AIDojoCoordinator.game_components import GameState, IP, Network, Data, Service +from NetSecGame.game_components import GameState, IP, Network, Data, Service # pytest fixtures for creating sample objects @pytest.fixture diff --git a/tests/components/test_ip.py b/tests/components/test_ip.py index 58fc8870..b7a60cb4 100644 --- a/tests/components/test_ip.py +++ b/tests/components/test_ip.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from AIDojoCoordinator.game_components import IP +from NetSecGame.game_components import IP # Pytest fixtures for creating sample IP objects @pytest.fixture diff --git a/tests/components/test_network.py b/tests/components/test_network.py index 5042e3c8..22657406 100644 --- a/tests/components/test_network.py +++ b/tests/components/test_network.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from AIDojoCoordinator.game_components import Network +from NetSecGame.game_components import Network # Pytest fixture for creating a sample Network object @pytest.fixture diff --git a/tests/components/test_service.py b/tests/components/test_service.py index 69f87e12..fc4164c4 100644 --- a/tests/components/test_service.py +++ b/tests/components/test_service.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from AIDojoCoordinator.game_components import Service +from NetSecGame.game_components import Service # Fixtures for Service objects @pytest.fixture diff --git a/tests/coordinator/test_agent_server.py b/tests/coordinator/test_agent_server.py index 5c90ea98..4b71622a 100644 --- a/tests/coordinator/test_agent_server.py +++ b/tests/coordinator/test_agent_server.py @@ -3,8 +3,8 @@ import pytest from unittest.mock import AsyncMock, MagicMock from contextlib import suppress -from AIDojoCoordinator.coordinator.coordinator import AgentServer -from AIDojoCoordinator.game_components import Action, ActionType, ProtocolConfig +from NetSecGame.coordinator.coordinator import AgentServer +from NetSecGame.game_components import Action, ActionType, ProtocolConfig # ----------------------- # Fixtures diff --git a/tests/coordinator/test_coordinator_core.py b/tests/coordinator/test_coordinator_core.py index 1af7b92d..ba1222cf 100644 --- a/tests/coordinator/test_coordinator_core.py +++ b/tests/coordinator/test_coordinator_core.py @@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, patch from types import SimpleNamespace -from AIDojoCoordinator.coordinator.coordinator import GameCoordinator -from AIDojoCoordinator.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus +from NetSecGame.coordinator.coordinator import GameCoordinator +from NetSecGame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus # ----------------------- # Fixtures diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index d60659b1..7a0774f7 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -1,6 +1,6 @@ import pytest -from AIDojoCoordinator.game_components import ActionType, Action -from AIDojoCoordinator.coordinator.global_defender import GlobalDefender +from NetSecGame.game_components import ActionType, Action +from NetSecGame.coordinator.global_defender import GlobalDefender from unittest.mock import patch @pytest.fixture diff --git a/tests/manual/three_nets/manual_test_three_net_scenario.py b/tests/manual/three_nets/manual_test_three_net_scenario.py index d1bec3ea..7082d67d 100644 --- a/tests/manual/three_nets/manual_test_three_net_scenario.py +++ b/tests/manual/three_nets/manual_test_three_net_scenario.py @@ -5,7 +5,7 @@ PATH = path.dirname( path.dirname( path.dirname( path.dirname( path.abspath(__file__) ) ) )) sys.path.append(path.dirname( path.dirname( path.dirname( path.dirname( path.abspath(__file__) ) ) ))) from NetSecGameAgents.agents import base_agent -from AIDojoCoordinator.game_components import Action, ActionType, IP, Network, Service, Data +from NetSecGame.game_components import Action, ActionType, IP, Network, Service, Data if __name__ == "__main__": From afe5369f594299099911212dd523ad206de49b9b Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:26:48 +0100 Subject: [PATCH 006/112] Implement BaseAgent class for network communication in NetSecGame (moved from Agents repository) --- NetSecGame/base_agent.py | 182 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 NetSecGame/base_agent.py diff --git a/NetSecGame/base_agent.py b/NetSecGame/base_agent.py new file mode 100644 index 00000000..185b0aa7 --- /dev/null +++ b/NetSecGame/base_agent.py @@ -0,0 +1,182 @@ +# Author: Ondrej Lukas, ondrej.lukas@aic.cvut.cz +# Basic agent class that is to be extended in each agent classes +import logging +import socket +import json +from abc import ABC + +from NetSecGame.game_components import Action, GameState, Observation, ActionType, GameStatus, AgentInfo, ProtocolConfig + +class BaseAgent(ABC): + """ + Author: Ondrej Lukas, ondrej.lukas@aic.cvut.cz + Basic agent for the network based NetSecGame environment. Implemenets communication with the game server. + """ + + def __init__(self, host, port, role:str)->None: + self._connection_details = (host, port) + self._logger = logging.getLogger(self.__class__.__name__) + self._role = role + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect((host, port)) + except socket.error as e: + self._logger.error(f"Socket error: {e}") + self.sock = None + self._logger.info("Agent created") + + def __del__(self): + "In case the extending class did not close the connection, terminate the socket when the object is deleted." + if self._socket: + try: + self._socket.close() + self._logger.info("Socket closed") + except socket.error as e: + print(f"Error closing socket: {e}") + + def terminate_connection(self)->None: + """Method for graceful termination of connection. Should be used by any class extending the BaseAgent.""" + if self._socket: + try: + self._socket.close() + self._socket = None + self._logger.info("Socket closed") + except socket.error as e: + print(f"Error closing socket: {e}") + @property + def socket(self)->socket.socket | None: + return self._socket + + @property + def role(self)->str: + return self._role + + @property + def logger(self)->logging.Logger: + return self._logger + + def make_step(self, action: Action) -> Observation | None: + """ + Executes a single step in the environment by sending the agent's action to the server and receiving the resulting observation. + + Args: + action (Action): The action to be performed by the agent. + + Returns: + Observation: The new observation received from the server, containing the updated game state, reward, end flag, and additional info. + None: If no observation is received from the server. + + Raises: + Any exceptions raised by the `communicate` method are propagated. + """ + _, observation_dict, _ = self.communicate(action) + if observation_dict: + return Observation(GameState.from_dict(observation_dict["state"]), observation_dict["reward"], observation_dict["end"], observation_dict["info"]) + else: + return None + + def communicate(self, data:Action)-> tuple: + """ + Exchanges data with the server and returns the server's response. + This method sends an `Action` object to the server and waits for a response. + The response is expected to be a JSON-encoded string containing status, observation, and message fields. + The method returns a tuple containing the parsed status, observation, and message. + Args: + data (Action): The action to send to the server. Must be an instance of `Action`. + Returns: + tuple: A tuple containing: + - status (GameStatus): The status object parsed from the server response. + - observation (dict): The observation data from the server. + - message (str or None): An optional message from the server. + Raises: + ValueError: If `data` is not of type `Action`. + ConnectionError: If the server response is incomplete or missing the end-of-message marker. + Exception: If there is an error sending data to the server. + """ + + def _send_data(socket, msg:str)->None: + try: + self._logger.debug(f'Sending: {msg}') + socket.sendall(msg.encode()) + except Exception as e: + self._logger.error(f'Exception in _send_data(): {e}') + raise e + + def _receive_data(socket)->tuple: + """ + Receive data from server + """ + # Receive data from the server + data = b"" # Initialize an empty byte string + + while True: + chunk = socket.recv(ProtocolConfig.BUFFER_SIZE) # Receive a chunk + if not chunk: # If no more data, break (connection closed) + break + data += chunk + if ProtocolConfig.END_OF_MESSAGE in data: # Check if EOF marker is present + break + if ProtocolConfig.END_OF_MESSAGE not in data: + raise ConnectionError("Unfinished connection.") + data = data.replace(ProtocolConfig.END_OF_MESSAGE, b"") # Remove EOF marker + data = data.decode() + self._logger.debug(f"Data received from env: {data}") + # extract data from string representation + data_dict = json.loads(data) + # Add default values if dict keys are missing + status = data_dict["status"] if "status" in data_dict else "" + observation = data_dict["observation"] if "observation" in data_dict else {} + message = data_dict["message"] if "message" in data_dict else None + + return GameStatus.from_string(str(status)), observation, message + + if isinstance(data, Action): + data = data.to_json() + else: + raise ValueError("Incorrect data type! Data should be ONLY of type Action") + + _send_data(self._socket, data) + return _receive_data(self._socket) + + def register(self)->Observation | None: + """ + Method for registering agent to the game server. + Classname is used as agent name and the role is based on the 'role' argument. + Returns initial observation if registration was successful, None otherwise. + + Args: + role (str): Role of the agent, either 'attacker' or 'defender'. + Returns: + Observation: Initial observation if registration was successful, None otherwise. + """ + try: + self._logger.info(f'Registering agent as {self.role}') + status, observation_dict, message = self.communicate(Action(ActionType.JoinGame, + parameters={"agent_info":AgentInfo(self.__class__.__name__,self.role)})) + if status is GameStatus.CREATED: + self._logger.info(f"\tRegistration successful! {message}") + return Observation(GameState.from_dict(observation_dict["state"]), observation_dict["reward"], observation_dict["end"], message) + else: + self._logger.error(f'\tRegistration failed! (status: {status}, msg:{message}') + return None + except Exception as e: + self._logger.error(f'Exception in register(): {e}') + + def request_game_reset(self, request_trajectory=False, randomize_topology=True, randomize_topology_seed=None) -> Observation|None: + """ + Requests a game reset from the server. Optionally requests a trajectory and/or topology randomization. + Args: + request_trajectory (bool): If True, requests the server to provide a trajectory of the last episode. + randomize_topology (bool): If True, requests the server to randomize the network topology for the next episode. Defaults to True. + randomize_topology_seed (int): If provided, requests the server to use this seed for randomizing the network topology. Defaults to None. + Returns: + Observation: The initial observation after the reset if successful, None otherwise. + """ + self._logger.debug("Requesting game reset") + status, observation_dict, message = self.communicate(Action(ActionType.ResetGame, parameters={"request_trajectory": request_trajectory, "randomize_topology": randomize_topology})) + if status: + self._logger.debug('\tReset successful') + return Observation(GameState.from_dict(observation_dict["state"]), observation_dict["reward"], observation_dict["end"], message) + else: + self._logger.error(f'\rReset failed! (status: {status}, msg:{message}') + return None \ No newline at end of file From 69940d77ad61748309c0dd163da89dbd557875f1 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:30:18 +0100 Subject: [PATCH 007/112] Add initial imports for NetSecGame package structure --- NetSecGame/__init__.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/NetSecGame/__init__.py b/NetSecGame/__init__.py index e69de29b..eaa80b7c 100644 --- a/NetSecGame/__init__.py +++ b/NetSecGame/__init__.py @@ -0,0 +1,22 @@ +# add imports so that they are available when importing the package NetSecGame +# e.g., from NetSecGame import GameState + +# Game components +from .game_components import ( + Action, + ActionType, + AgentInfo, + Data, + GameState, + GameStatus, + IP, + Data, + Network, + Observation, + ProtocolConfig, + Service +) +# Base agent +from .base_agent import BaseAgent + +# Selected util functions \ No newline at end of file From dca1aefaf6916568a6090a574384f661a1a2c48a Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:38:44 +0100 Subject: [PATCH 008/112] Deprecate read_replay_buffer_from_csv and store_replay_buffer_in_csv functions; add read_trajectories_from_jsonl with NotImplementedError --- NetSecGame/utils/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NetSecGame/utils/utils.py b/NetSecGame/utils/utils.py index eaa089d3..bb99aa24 100644 --- a/NetSecGame/utils/utils.py +++ b/NetSecGame/utils/utils.py @@ -45,6 +45,7 @@ def read_replay_buffer_from_csv(csvfile:str)->list: expected colums in the csv: state_t0, action_t0, reward_t1, state_t1, done_t1 """ + raise DeprecationWarning("This function is deprecated and will be removed in future versions.") buffer = [] try: with open(csvfile, 'r') as f_object: @@ -62,6 +63,7 @@ def store_replay_buffer_in_csv(replay_buffer:list, filename:str, delimiter:str=" Expected format of replay buffer items: (state_t0:GameState, action_t0:Action, reward_t1:float, state_t1:GameState, done_t1:bool) """ + raise DeprecationWarning("This function is deprecated and will be removed in future versions.") with open(filename, 'a') as f_object: writer_object = csv.writer(f_object, delimiter=delimiter) for (s_t, a_t, r, s_t1, done) in replay_buffer: @@ -178,6 +180,16 @@ def store_trajectories_to_jsonl(trajectories:list, dir:str, filename:str)->None: with jsonlines.open(filename, "a") as writer: writer.write(trajectories) +def read_trajectories_from_jsonl(filepath:str)->list: + """ + Read trajectories from a JSONL file. + Args: + filepath (str): Path to the JSONL file. + Returns: + list: List of trajectories read from the file. + """ + raise NotImplementedError("This function is not yet implemented.") + if __name__ == "__main__": state = GameState(known_networks={Network("1.1.1.1", 24),Network("1.1.1.2", 24)}, known_hosts={IP("192.168.1.2"), IP("192.168.1.3")}, controlled_hosts={IP("192.168.1.2")}, From 521875647428e420d35a49611a5c057415e46626 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:38:56 +0100 Subject: [PATCH 009/112] Add missing newline at end of file in __init__.py and ensure proper import structure for utils --- NetSecGame/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NetSecGame/__init__.py b/NetSecGame/__init__.py index eaa80b7c..2006cfcb 100644 --- a/NetSecGame/__init__.py +++ b/NetSecGame/__init__.py @@ -19,4 +19,12 @@ # Base agent from .base_agent import BaseAgent -# Selected util functions \ No newline at end of file +# Selected util functions +from .utils.utils import ( + get_file_hash, + state_as_ordered_string, + store_trajectories_to_jsonl, + read_trajectories_from_jsonl, + observation_as_dict, + observation_to_str +) \ No newline at end of file From 02e88219c0f8d63b05f450a69cc3f0244c2cd7bc Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 17:51:50 +0100 Subject: [PATCH 010/112] Define public API for NetSecGame package and ensure proper formatting --- NetSecGame/__init__.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/NetSecGame/__init__.py b/NetSecGame/__init__.py index 2006cfcb..f07e291e 100644 --- a/NetSecGame/__init__.py +++ b/NetSecGame/__init__.py @@ -27,4 +27,25 @@ read_trajectories_from_jsonl, observation_as_dict, observation_to_str -) \ No newline at end of file +) +# Define the public API of the package +__all__ = [ + "Action", + "ActionType", + "AgentInfo", + "Data", + "GameState", + "GameStatus", + "IP", + "Network", + "Observation", + "ProtocolConfig", + "Service", + "BaseAgent", + "get_file_hash", + "state_as_ordered_string", + "store_trajectories_to_jsonl", + "read_trajectories_from_jsonl", + "observation_as_dict", + "observation_to_str", +] \ No newline at end of file From 8d336fd79fa3cc2ebfb4df1d287fe7564a3dbcad Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 18:17:32 +0100 Subject: [PATCH 011/112] Move scenarios --- NetSecGame/coordinator/config_parser.py | 16 ++++---- .../{ => coordinator}/scenarios/__init__.py | 0 .../{ => coordinator}/scenarios/one_net.py | 0 .../scenarios/scenario_configuration.py | 0 .../smaller_scenario_configuration.py | 0 .../scenarios/test_scenario_configuration.py | 0 .../scenarios/three_net_scenario.py | 0 .../scenarios/tiny_scenario_configuration.py | 0 .../{ => coordinator}/scenarios/two_nets.py | 0 .../scenarios/two_nets_small.py | 0 .../scenarios/two_nets_tiny.py | 0 pyproject.toml | 39 +++++++++++++++++-- 12 files changed, 43 insertions(+), 12 deletions(-) rename NetSecGame/{ => coordinator}/scenarios/__init__.py (100%) rename NetSecGame/{ => coordinator}/scenarios/one_net.py (100%) rename NetSecGame/{ => coordinator}/scenarios/scenario_configuration.py (100%) rename NetSecGame/{ => coordinator}/scenarios/smaller_scenario_configuration.py (100%) rename NetSecGame/{ => coordinator}/scenarios/test_scenario_configuration.py (100%) rename NetSecGame/{ => coordinator}/scenarios/three_net_scenario.py (100%) rename NetSecGame/{ => coordinator}/scenarios/tiny_scenario_configuration.py (100%) rename NetSecGame/{ => coordinator}/scenarios/two_nets.py (100%) rename NetSecGame/{ => coordinator}/scenarios/two_nets_small.py (100%) rename NetSecGame/{ => coordinator}/scenarios/two_nets_tiny.py (100%) diff --git a/NetSecGame/coordinator/config_parser.py b/NetSecGame/coordinator/config_parser.py index 96dceed4..a3650d18 100644 --- a/NetSecGame/coordinator/config_parser.py +++ b/NetSecGame/coordinator/config_parser.py @@ -354,14 +354,14 @@ def get_scenario(self): Get the scenario config objects based on the configuration. Only import objects that are selected via importlib. """ allowed_names = { - "scenario1" : "AIDojoCoordinator.scenarios.scenario_configuration", - "scenario1_small" : "AIDojoCoordinator.scenarios.smaller_scenario_configuration", - "scenario1_tiny" : "AIDojoCoordinator.scenarios.tiny_scenario_configuration", - "one_network": "AIDojoCoordinator.scenarios.one_net", - "three_net_scenario": "AIDojoCoordinator.scenarios.three_net_scenario", - "two_networks": "AIDojoCoordinator.scenarios.two_nets", # same as scenario1 - "two_networks_small": "AIDojoCoordinator.scenarios.two_nets_small", # same as scenario1_small - "two_networks_tiny": "AIDojoCoordinator.scenarios.two_nets_tiny", # same as scenario1_small + "scenario1" : "NetSecGame.coordinator.scenarios.scenario_configuration", + "scenario1_small" : "NetSecGame.coordinator.scenarios.smaller_scenario_configuration", + "scenario1_tiny" : "NetSecGame.coordinator.scenarios.tiny_scenario_configuration", + "one_network": "NetSecGame.coordinator.scenarios.one_net", + "three_net_scenario": "NetSecGame.coordinator.scenarios.three_net_scenario", + "two_networks": "NetSecGame.coordinator.scenarios.two_nets", # same as scenario1 + "two_networks_small": "NetSecGame.coordinator.scenarios.two_nets_small", # same as scenario1_small + "two_networks_tiny": "NetSecGame.coordinator.scenarios.two_nets_tiny", # same as scenario1_small } scenario_name = self.config['env']['scenario'] diff --git a/NetSecGame/scenarios/__init__.py b/NetSecGame/coordinator/scenarios/__init__.py similarity index 100% rename from NetSecGame/scenarios/__init__.py rename to NetSecGame/coordinator/scenarios/__init__.py diff --git a/NetSecGame/scenarios/one_net.py b/NetSecGame/coordinator/scenarios/one_net.py similarity index 100% rename from NetSecGame/scenarios/one_net.py rename to NetSecGame/coordinator/scenarios/one_net.py diff --git a/NetSecGame/scenarios/scenario_configuration.py b/NetSecGame/coordinator/scenarios/scenario_configuration.py similarity index 100% rename from NetSecGame/scenarios/scenario_configuration.py rename to NetSecGame/coordinator/scenarios/scenario_configuration.py diff --git a/NetSecGame/scenarios/smaller_scenario_configuration.py b/NetSecGame/coordinator/scenarios/smaller_scenario_configuration.py similarity index 100% rename from NetSecGame/scenarios/smaller_scenario_configuration.py rename to NetSecGame/coordinator/scenarios/smaller_scenario_configuration.py diff --git a/NetSecGame/scenarios/test_scenario_configuration.py b/NetSecGame/coordinator/scenarios/test_scenario_configuration.py similarity index 100% rename from NetSecGame/scenarios/test_scenario_configuration.py rename to NetSecGame/coordinator/scenarios/test_scenario_configuration.py diff --git a/NetSecGame/scenarios/three_net_scenario.py b/NetSecGame/coordinator/scenarios/three_net_scenario.py similarity index 100% rename from NetSecGame/scenarios/three_net_scenario.py rename to NetSecGame/coordinator/scenarios/three_net_scenario.py diff --git a/NetSecGame/scenarios/tiny_scenario_configuration.py b/NetSecGame/coordinator/scenarios/tiny_scenario_configuration.py similarity index 100% rename from NetSecGame/scenarios/tiny_scenario_configuration.py rename to NetSecGame/coordinator/scenarios/tiny_scenario_configuration.py diff --git a/NetSecGame/scenarios/two_nets.py b/NetSecGame/coordinator/scenarios/two_nets.py similarity index 100% rename from NetSecGame/scenarios/two_nets.py rename to NetSecGame/coordinator/scenarios/two_nets.py diff --git a/NetSecGame/scenarios/two_nets_small.py b/NetSecGame/coordinator/scenarios/two_nets_small.py similarity index 100% rename from NetSecGame/scenarios/two_nets_small.py rename to NetSecGame/coordinator/scenarios/two_nets_small.py diff --git a/NetSecGame/scenarios/two_nets_tiny.py b/NetSecGame/coordinator/scenarios/two_nets_tiny.py similarity index 100% rename from NetSecGame/scenarios/two_nets_tiny.py rename to NetSecGame/coordinator/scenarios/two_nets_tiny.py diff --git a/pyproject.toml b/pyproject.toml index 318382db..a7c12858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "AIDojoGameCoordinator" -version = "0.1.0" +name = "NetSecGame" +version = "0.2.0" description = "A package for coordinating AI-driven network simulation games." readme = "README.md" license = { file = "LICENSE" } @@ -14,6 +14,39 @@ authors = [ { name = "Maria Rigaki", email = "maria.rigaki@aic.fel.cvut.cz" } ] dependencies = [ + "aiohttp==3.11.8", + "attrs==23.2.0", + "beartype==0.19.0", + "cachetools==5.5.0", + "casefy==0.1.7", + "cyst==0.3.4", + "dictionaries==0.0.2", + "Faker==23.2.1", + "Jinja2==3.1.4", + "jsonlines==4.0.0", + "kaleido==0.2.1", + "MarkupSafe==3.0.2", + "matplotlib==3.9.1", + "netaddr==0.9.0", + "numpy==1.26.4", + "pandas==2.2.2", + "plotly==5.22.0", + "pyserde==0.21.0", + "python-dateutil==2.8.2", + "PyYAML==6.0.1", + "redis==3.5.3", + "requests==2.32.3", + "scikit-learn==1.5.1", + "scipy==1.14.0", + "tenacity==8.5.0", + "typing-inspect==0.9.0", + "typing_extensions==4.12.2", +] +requires-python = ">=3.12" + +[project.optional-dependencies] + +server = [ "aiohttp==3.11.8", "attrs==23.2.0", "beartype==0.19.0", @@ -45,9 +78,7 @@ dependencies = [ "typing_extensions==4.12.2", "cyst-core>=0.5.0" ] -requires-python = ">=3.12" -[project.optional-dependencies] dev = [ "pytest", "ruff", From 67fb6c9e2f9a6960dc1d444b442a6829b16f1a63 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 7 Jan 2026 18:20:09 +0100 Subject: [PATCH 012/112] Fix import path for random patch in test_mock_stochastic_probabilities --- tests/coordinator/test_global_defender.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index 7a0774f7..65db243c 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -56,7 +56,7 @@ def test_mock_stochastic_probabilities(defender, episode_actions): """Test stochastic function is only called when thresholds are crossed.""" action = Action(ActionType.ScanNetwork, {}) episode_actions += [{"action_type": str(ActionType.ScanNetwork)}] * 4 # Exceed threshold - - with patch("AIDojoCoordinator.global_defender.random", return_value=0.01): # Force detection probability + + with patch("NetSecGame.coordinator.global_defender.random", return_value=0.01): # Force detection probability result = defender.stochastic_with_threshold(action, episode_actions, tw_size=5) assert result # Should be True since we forced a low probability value \ No newline at end of file From a0591bc47961f771cf8001b7240b50d9a2b91d2d Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 12:48:18 +0100 Subject: [PATCH 013/112] Add Windows Docker run instructions to README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index ab640482..18f15eed 100755 --- a/README.md +++ b/README.md @@ -132,6 +132,15 @@ docker run -it --rm \ --debug_level="WARNING" ``` +#### Running on Windows (with Docker Desktop): +```cmd +docker run -d --rm --name netsecgame-server^ + -p 9000:9000 ^ + -v "%cd%\examples\example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml" ^ + -v "%cd%\logs:/aidojo/logs" ^ + stratosphereips/netsecgame +``` + ## Documentation You can find user documentation at [https://stratosphereips.github.io/NetSecGame/](https://stratosphereips.github.io/NetSecGame/) ## Components of the NetSecGame Environment From fc8f47d54a9a599fabcdd20cc7552b09661f5197 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 13:04:01 +0100 Subject: [PATCH 014/112] Update Docker run commands in README to specify the latest image tag --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18f15eed..ec179492 100755 --- a/README.md +++ b/README.md @@ -128,17 +128,17 @@ optionally, you can set the logging level with `--debug_level=["DEBUG", "INFO", docker run -it --rm \ -v $(pwd)/examples/example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml \ -v $(pwd)/logs:/aidojo/logs \ - -p 9000:9000 stratosphereips/netsecgame \ + -p 9000:9000 stratosphereips/netsecgame:latest \ --debug_level="WARNING" ``` #### Running on Windows (with Docker Desktop): ```cmd -docker run -d --rm --name netsecgame-server^ +docker run -d --rm --name netsecgame-server ^ -p 9000:9000 ^ -v "%cd%\examples\example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml" ^ -v "%cd%\logs:/aidojo/logs" ^ - stratosphereips/netsecgame + stratosphereips/netsecgame:latest ``` ## Documentation From 534b7c393a64101f574cf3ab1d95cb3f44180d58 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 13:19:37 +0100 Subject: [PATCH 015/112] Refactor CYSTCoordinator: Move get_starting_position_from_cyst_config to its own function and enhance logic for extracting starting positions --- .../coordinator/worlds/CYSTCoordinator.py | 29 +++++++++++++++++-- NetSecGame/utils/utils.py | 20 ------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/NetSecGame/coordinator/worlds/CYSTCoordinator.py b/NetSecGame/coordinator/worlds/CYSTCoordinator.py index 0d34877d..a6e06164 100644 --- a/NetSecGame/coordinator/worlds/CYSTCoordinator.py +++ b/NetSecGame/coordinator/worlds/CYSTCoordinator.py @@ -8,10 +8,35 @@ import logging import argparse from pathlib import Path -from NetSecGame.game_components import GameState, Action, ActionType, IP, Service +from NetSecGame.game_components import GameState, Action, ActionType, IP, Service ,Network from NetSecGame.coordinator.coordinator import GameCoordinator +from cyst.api.configuration.network.node import NodeConfig -from NetSecGame.utils.utils import get_starting_position_from_cyst_config, get_logging_level +from NetSecGame.utils.utils import get_logging_level + +def get_starting_position_from_cyst_config(cyst_objects): + """ + Extracts starting positions from CYST configuration objects. + + Args: + cyst_objects (list): List of CYST configuration objects. + Returns: + dict: A dictionary mapping agent identifiers to their starting known hosts and networks. + """ + starting_positions = {} + for obj in cyst_objects: + if isinstance(obj, NodeConfig): + for active_service in obj.active_services: + if active_service.type == "netsecenv_agent": + print(f"starting processing {obj.id}.{active_service.name}") + hosts = set() + networks = set() + for interface in obj.interfaces: + hosts.add(IP(str(interface.ip))) + net_ip, net_mask = str(interface.net).split("/") + networks.add(Network(net_ip,int(net_mask))) + starting_positions[f"{obj.id}.{active_service.name}"] = {"known_hosts":hosts, "known_networks":networks} + return starting_positions class CYSTCoordinator(GameCoordinator): diff --git a/NetSecGame/utils/utils.py b/NetSecGame/utils/utils.py index bb99aa24..5d036691 100644 --- a/NetSecGame/utils/utils.py +++ b/NetSecGame/utils/utils.py @@ -2,11 +2,8 @@ # Author: Sebastian Garcia. sebastian.garcia@agents.fel.cvut.cz # Author: Ondrej Lukas, ondrej.lukas@aic.fel.cvut.cz -import yaml # This is used so the agent can see the environment and game components -import importlib from NetSecGame.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType -import netaddr import logging import csv import os @@ -14,7 +11,6 @@ from random import randint import json import hashlib -from cyst.api.configuration.network.node import NodeConfig from typing import Optional def get_file_hash(filepath, hash_func='sha256', chunk_size=4096): @@ -147,22 +143,6 @@ def get_logging_level(debug_level): level = log_levels.get(debug_level.upper(), logging.ERROR) return level -def get_starting_position_from_cyst_config(cyst_objects): - starting_positions = {} - for obj in cyst_objects: - if isinstance(obj, NodeConfig): - for active_service in obj.active_services: - if active_service.type == "netsecenv_agent": - print(f"starting processing {obj.id}.{active_service.name}") - hosts = set() - networks = set() - for interface in obj.interfaces: - hosts.add(IP(str(interface.ip))) - net_ip, net_mask = str(interface.net).split("/") - networks.add(Network(net_ip,int(net_mask))) - starting_positions[f"{obj.id}.{active_service.name}"] = {"known_hosts":hosts, "known_networks":networks} - return starting_positions - def store_trajectories_to_jsonl(trajectories:list, dir:str, filename:str)->None: """ Store trajectories to a JSONL file. From 4825fcdd6e795c87f16053cc6ee880ac7d6ecf47 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 14:15:08 +0100 Subject: [PATCH 016/112] Refactor utils.py: Organize imports and enhance hash function documentation --- NetSecGame/utils/utils.py | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/NetSecGame/utils/utils.py b/NetSecGame/utils/utils.py index 5d036691..3a96ead7 100644 --- a/NetSecGame/utils/utils.py +++ b/NetSecGame/utils/utils.py @@ -1,21 +1,39 @@ # Utility functions for then env and for the agents # Author: Sebastian Garcia. sebastian.garcia@agents.fel.cvut.cz # Author: Ondrej Lukas, ondrej.lukas@aic.fel.cvut.cz - -# This is used so the agent can see the environment and game components -from NetSecGame.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType -import logging +# --- Standard Library Imports --- import csv +import hashlib +import json +import logging import os -import jsonlines from random import randint -import json -import hashlib -from typing import Optional +from typing import Optional + +# --- Third-Party Imports --- +import jsonlines + +# --- Local Application/Library Specific Imports --- +from NetSecGame.game_components import ( + Action, + ActionType, + Data, + GameState, + IP, + Network, + Observation, + Service, +) def get_file_hash(filepath, hash_func='sha256', chunk_size=4096): """ Computes hash of a given file. + Args: + filepath (str): The path to the file to hash. + hash_func (str): The hash function to use (default is 'sha256'). + chunk_size (int): The size of each chunk to read from the file (default is 4096 bytes). + Returns: + str: The hexadecimal hash of the file. """ hash_algorithm = hashlib.new(hash_func) with open(filepath, 'rb') as file: @@ -25,9 +43,14 @@ def get_file_hash(filepath, hash_func='sha256', chunk_size=4096): chunk = file.read(chunk_size) return hash_algorithm.hexdigest() -def get_str_hash(string, hash_func='sha256', chunk_size=4096): +def get_str_hash(string, hash_func='sha256'): """ - Computes hash of a given file. + Computes hash of a given string. + Args: + string (str): The input string to hash. + hash_func (str): The hash function to use (default is 'sha256'). + Returns: + str: The hexadecimal hash of the input string. """ hash_algorithm = hashlib.new(hash_func) hash_algorithm.update(string.encode('utf-8')) From ac592880955ac5d6d50153cbe06f74d6974119c6 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 14:16:39 +0100 Subject: [PATCH 017/112] Refactor utils.py: Remove unused import for randint and clean up import section --- NetSecGame/utils/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NetSecGame/utils/utils.py b/NetSecGame/utils/utils.py index 3a96ead7..846e3d19 100644 --- a/NetSecGame/utils/utils.py +++ b/NetSecGame/utils/utils.py @@ -7,13 +7,12 @@ import json import logging import os -from random import randint from typing import Optional # --- Third-Party Imports --- import jsonlines -# --- Local Application/Library Specific Imports --- +# --- Local Imports --- from NetSecGame.game_components import ( Action, ActionType, From f36b13114d7a591ff628cb31ff49e388d2edd380 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 14:41:29 +0100 Subject: [PATCH 018/112] Refactor pyproject.toml: Clean up dependencies and add server dependency to dev section --- pyproject.toml | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a7c12858..647b15b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,39 +13,16 @@ authors = [ { name = "Sebastian Garcia", email = "sebastian.garcia@agents.fel.cvut.cz" }, { name = "Maria Rigaki", email = "maria.rigaki@aic.fel.cvut.cz" } ] +# light-weight version (allows running and development of agents) dependencies = [ - "aiohttp==3.11.8", - "attrs==23.2.0", - "beartype==0.19.0", - "cachetools==5.5.0", - "casefy==0.1.7", - "cyst==0.3.4", - "dictionaries==0.0.2", - "Faker==23.2.1", - "Jinja2==3.1.4", - "jsonlines==4.0.0", - "kaleido==0.2.1", - "MarkupSafe==3.0.2", - "matplotlib==3.9.1", - "netaddr==0.9.0", - "numpy==1.26.4", - "pandas==2.2.2", - "plotly==5.22.0", - "pyserde==0.21.0", - "python-dateutil==2.8.2", - "PyYAML==6.0.1", - "redis==3.5.3", - "requests==2.32.3", - "scikit-learn==1.5.1", - "scipy==1.14.0", - "tenacity==8.5.0", - "typing-inspect==0.9.0", - "typing_extensions==4.12.2", + "jsonlines>=4.0.0", + "netaddr>=0.9.0", ] requires-python = ">=3.12" [project.optional-dependencies] +# dependencies allowing to run the game server and the simulation server = [ "aiohttp==3.11.8", "attrs==23.2.0", @@ -80,6 +57,7 @@ server = [ ] dev = [ + "NetSecGame[server]", "pytest", "ruff", "pytest-asyncio" From 0e7380ef4dfad6f17fa1955c4b6ef88867ea0afb Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 14:59:02 +0100 Subject: [PATCH 019/112] Refactor Dockerfile and README: Update destination directory from /aidojo to /netsecgame for consistency --- Dockerfile | 2 +- README.md | 44 +++++++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3be8548a..d7f0f6a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.12.10-slim # Set the working directory in the container -ENV DESTINATION_DIR=/aidojo +ENV DESTINATION_DIR=/netsecgame # Install system dependencies diff --git a/README.md b/README.md index ec179492..c94210e5 100755 --- a/README.md +++ b/README.md @@ -118,16 +118,16 @@ Upon which the game server is created on `localhost:9000` to which the agents ca When running in the Docker container, the NetSecGame can be started with: ```bash docker run -it --rm \ - -v $(pwd)/examples/example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml \ - -v $(pwd)/logs:/aidojo/logs \ + -v $(pwd)/examples/example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml \ + -v $(pwd)/logs:/netsecgame/logs \ -p 9000:9000 stratosphereips/netsecgame ``` optionally, you can set the logging level with `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): ```bash docker run -it --rm \ - -v $(pwd)/examples/example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml \ - -v $(pwd)/logs:/aidojo/logs \ + -v $(pwd)/examples/example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml \ + -v $(pwd)/logs:/netsecgame/logs \ -p 9000:9000 stratosphereips/netsecgame:latest \ --debug_level="WARNING" ``` @@ -136,8 +136,8 @@ docker run -it --rm \ ```cmd docker run -d --rm --name netsecgame-server ^ -p 9000:9000 ^ - -v "%cd%\examples\example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml" ^ - -v "%cd%\logs:/aidojo/logs" ^ + -v "%cd%\examples\example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml" ^ + -v "%cd%\logs:/netsecgame/logs" ^ stratosphereips/netsecgame:latest ``` @@ -147,27 +147,29 @@ You can find user documentation at [https://stratosphereips.github.io/NetSecGame The architecture of the environment can be seen [here](docs/Architecture.md). The NetSecGame environment has several components in the following files: ``` -├── AIDojoGameCoordinator/ -| ├── game_coordinator.py -| ├── game_components.py -| ├── global_defender.py -| ├── worlds/ -| ├── NSGCoordinator.py -| ├── NSGRealWorldCoordinator.py -| ├── CYSTCoordinator.py -| ├── scenarios/ -| ├── tiny_scenario_configuration.py -| ├── smaller_scenario_configuration.py -| ├── scenario_configuration.py -| ├── three_net_configuration.py +├── NetSecgame/ +| ├── base_agent.py # Basic agent class. Defines the API for agent-server communication +| ├── game_components.py # contains basic building blocks of the environment | ├── utils/ | ├── utils.py | ├── log_parser.py | ├── gamaplay_graphs.py | ├── actions_parser.py +| ├── coordinator/ # contains components required for running the game server +| ├── scenarios/ +| ├── tiny_scenario_configuration.py +| ├── smaller_scenario_configuration.py +| ├── scenario_configuration.py +| ├── three_net_configuration.py +| ├── worlds/ +| ├── NSGCoordinator.py # basic simulation, pure NSG +| ├── NSGRealWorldCoordinator.py # Extension of `NSGCoordinator` - runs actions in the *network of the host computer* +| ├── CYSTCoordinator.py # Extension of `NSGCoordinator` - runs simulation in CYST engine. +| ├── WhiteBoxNSGCoordinator.py # Extension of `NSGCoordinator` - provides agents with full list of actions upon registration. +| ├── config_parser.py # NSG task configuration parser +| ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) +| ├── global_defender.py # Stochastic (non-agentic defender) ``` - - ### Directory Details - `coordinator.py`: Basic coordinator class. Handles agent communication and coordination. **Does not implement dynamics of the world** and must be extended (see examples in `worlds/`). - `game_components.py`: Implements a library with objects used in the environment. See [detailed explanation](AIDojoCoordinator/docs/Components.md) of the game components. From 6fd90976562172003365b27129fee87aff168d4e Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 15:06:32 +0100 Subject: [PATCH 020/112] Refactor __init__.py: Add version metadata and organize public API structure --- NetSecGame/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NetSecGame/__init__.py b/NetSecGame/__init__.py index f07e291e..f3d11383 100644 --- a/NetSecGame/__init__.py +++ b/NetSecGame/__init__.py @@ -1,6 +1,8 @@ # add imports so that they are available when importing the package NetSecGame # e.g., from NetSecGame import GameState +__version__ = "0.1.0" + # Game components from .game_components import ( Action, @@ -28,8 +30,12 @@ observation_as_dict, observation_to_str ) + # Define the public API of the package __all__ = [ + # Metadata + "__version__", + # Game components "Action", "ActionType", "AgentInfo", @@ -41,7 +47,9 @@ "Observation", "ProtocolConfig", "Service", + # Base agent "BaseAgent", + # Utils "get_file_hash", "state_as_ordered_string", "store_trajectories_to_jsonl", From 29d637a6d0ead617d488eaa9476a96fd0ebc662f Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 15:28:09 +0100 Subject: [PATCH 021/112] Refactor pyproject.toml: Update license format, enable dynamic versioning, and refine package exclusions --- pyproject.toml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 647b15b8..de4d08b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,11 @@ build-backend = "setuptools.build_meta" [project] name = "NetSecGame" -version = "0.2.0" +dynamic = ["version"] description = "A package for coordinating AI-driven network simulation games." readme = "README.md" -license = { file = "LICENSE" } +license = "GPL-3.0-or-later" +license-files = ["LICENSE"] authors = [ { name = "Ondrej Lukas", email = "ondrej.lukas@aic.fel.cvut.cz" }, { name = "Sebastian Garcia", email = "sebastian.garcia@agents.fel.cvut.cz" }, @@ -70,6 +71,9 @@ docs = [ "pymdown-extensions" ] +[tool.setuptools.dynamic] +version = { attr = "NetSecGame.__version__" } + [project.urls] Homepage = "https://github.com/stratosphereips/NetSecGame" Repository = "https://github.com/stratosphereips/NetSecGame" @@ -78,7 +82,17 @@ Issues = "https://github.com/stratosphereips/NetSecGame/issues" [tool.setuptools.packages.find] where = ["."] -exclude = ["tests*"] +include = ["NetSecGame*"] +exclude = [ + "tests*", + "notebooks*", + "site*", + "docs*", + "logs*", + "mkdocs.yml", + "Dockerfile", + "NetSecGameAgents*" +] [tool.pytest.ini_options] testpaths = ["tests"] From 32bda73022602e1d0f83d93a9a9fe960fec931ac Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 15:38:35 +0100 Subject: [PATCH 022/112] Rename NetSecGame to netsecgame to comply with PEP8 --- {NetSecGame => netsecgame}/__init__.py | 0 {NetSecGame => netsecgame}/base_agent.py | 15 +++++++++++++-- .../coordinator/__init__.py | 0 .../coordinator/config_parser.py | 2 +- .../coordinator/coordinator.py | 8 ++++---- .../coordinator/global_defender.py | 2 +- .../coordinator/netsecenv_conf.yaml | 0 .../netsecevn_conf_cyst_integration.yaml | 0 .../coordinator/scenarios/__init__.py | 0 .../coordinator/scenarios/one_net.py | 0 .../scenarios/scenario_configuration.py | 0 .../scenarios/smaller_scenario_configuration.py | 0 .../scenarios/test_scenario_configuration.py | 0 .../coordinator/scenarios/three_net_scenario.py | 0 .../scenarios/tiny_scenario_configuration.py | 0 .../coordinator/scenarios/two_nets.py | 0 .../coordinator/scenarios/two_nets_small.py | 0 .../coordinator/scenarios/two_nets_tiny.py | 0 .../coordinator/worlds/CYSTCoordinator.py | 6 +++--- .../coordinator/worlds/NSEGameCoordinator.py | 6 +++--- .../worlds/NSGRealWorldCoordinator.py | 6 +++--- .../coordinator/worlds/WhiteBoxNSGCoordinator.py | 6 +++--- .../coordinator/worlds/__init__.py | 0 {NetSecGame => netsecgame}/docs/Components.md | 0 {NetSecGame => netsecgame}/docs/Coordinator.md | 0 .../docs/Trajectory_analysis.md | 0 .../docs/figures/architecture_diagram.jpg | Bin .../docs/figures/message_passing_coordinator.jpg | Bin .../docs/figures/scenarios/scenario 1_small.png | Bin .../docs/figures/scenarios/scenario_1.png | Bin .../docs/figures/scenarios/scenario_1_tiny.png | Bin .../docs/figures/scenarios/three_nets.png | Bin {NetSecGame => netsecgame}/game_components.py | 0 {NetSecGame => netsecgame}/utils/__init__.py | 0 {NetSecGame => netsecgame}/utils/action_plots.r | 0 .../utils/action_plots_readme.md | 0 .../utils/actions_parser.py | 0 .../utils/aidojo_log_colorizer.py | 0 .../utils/gamaplay_graphs.py | 2 +- {NetSecGame => netsecgame}/utils/log_parser.py | 0 .../utils/trajectory_analysis.py | 2 +- {NetSecGame => netsecgame}/utils/utils.py | 2 +- pyproject.toml | 6 +++--- 43 files changed, 37 insertions(+), 26 deletions(-) rename {NetSecGame => netsecgame}/__init__.py (100%) rename {NetSecGame => netsecgame}/base_agent.py (94%) rename {NetSecGame => netsecgame}/coordinator/__init__.py (100%) rename {NetSecGame => netsecgame}/coordinator/config_parser.py (99%) rename {NetSecGame => netsecgame}/coordinator/coordinator.py (99%) rename {NetSecGame => netsecgame}/coordinator/global_defender.py (98%) rename {NetSecGame => netsecgame}/coordinator/netsecenv_conf.yaml (100%) rename {NetSecGame => netsecgame}/coordinator/netsecevn_conf_cyst_integration.yaml (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/__init__.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/one_net.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/scenario_configuration.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/smaller_scenario_configuration.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/test_scenario_configuration.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/three_net_scenario.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/tiny_scenario_configuration.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/two_nets.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/two_nets_small.py (100%) rename {NetSecGame => netsecgame}/coordinator/scenarios/two_nets_tiny.py (100%) rename {NetSecGame => netsecgame}/coordinator/worlds/CYSTCoordinator.py (98%) rename {NetSecGame => netsecgame}/coordinator/worlds/NSEGameCoordinator.py (99%) rename {NetSecGame => netsecgame}/coordinator/worlds/NSGRealWorldCoordinator.py (97%) rename {NetSecGame => netsecgame}/coordinator/worlds/WhiteBoxNSGCoordinator.py (97%) rename {NetSecGame => netsecgame}/coordinator/worlds/__init__.py (100%) rename {NetSecGame => netsecgame}/docs/Components.md (100%) rename {NetSecGame => netsecgame}/docs/Coordinator.md (100%) rename {NetSecGame => netsecgame}/docs/Trajectory_analysis.md (100%) rename {NetSecGame => netsecgame}/docs/figures/architecture_diagram.jpg (100%) rename {NetSecGame => netsecgame}/docs/figures/message_passing_coordinator.jpg (100%) rename {NetSecGame => netsecgame}/docs/figures/scenarios/scenario 1_small.png (100%) rename {NetSecGame => netsecgame}/docs/figures/scenarios/scenario_1.png (100%) rename {NetSecGame => netsecgame}/docs/figures/scenarios/scenario_1_tiny.png (100%) rename {NetSecGame => netsecgame}/docs/figures/scenarios/three_nets.png (100%) rename {NetSecGame => netsecgame}/game_components.py (100%) rename {NetSecGame => netsecgame}/utils/__init__.py (100%) rename {NetSecGame => netsecgame}/utils/action_plots.r (100%) rename {NetSecGame => netsecgame}/utils/action_plots_readme.md (100%) rename {NetSecGame => netsecgame}/utils/actions_parser.py (100%) rename {NetSecGame => netsecgame}/utils/aidojo_log_colorizer.py (100%) rename {NetSecGame => netsecgame}/utils/gamaplay_graphs.py (99%) rename {NetSecGame => netsecgame}/utils/log_parser.py (100%) rename {NetSecGame => netsecgame}/utils/trajectory_analysis.py (99%) rename {NetSecGame => netsecgame}/utils/utils.py (99%) diff --git a/NetSecGame/__init__.py b/netsecgame/__init__.py similarity index 100% rename from NetSecGame/__init__.py rename to netsecgame/__init__.py diff --git a/NetSecGame/base_agent.py b/netsecgame/base_agent.py similarity index 94% rename from NetSecGame/base_agent.py rename to netsecgame/base_agent.py index 185b0aa7..1569c054 100644 --- a/NetSecGame/base_agent.py +++ b/netsecgame/base_agent.py @@ -5,7 +5,7 @@ import json from abc import ABC -from NetSecGame.game_components import Action, GameState, Observation, ActionType, GameStatus, AgentInfo, ProtocolConfig +from netsecgame.game_components import Action, GameState, Observation, ActionType, GameStatus, AgentInfo, ProtocolConfig class BaseAgent(ABC): """ @@ -179,4 +179,15 @@ def request_game_reset(self, request_trajectory=False, randomize_topology=True, return Observation(GameState.from_dict(observation_dict["state"]), observation_dict["reward"], observation_dict["end"], message) else: self._logger.error(f'\rReset failed! (status: {status}, msg:{message}') - return None \ No newline at end of file + return None + +if __name__ == "__main__": + # Example usage of BaseAgent + GAME_PORT = 5000 # Change to the appropriate port + agent = BaseAgent("localhost", GAME_PORT, "Attacker") + # Register the agent + observation = agent.register() + if observation: + print("Initial Observation:", observation) + # Gracefully terminate the connection + agent.terminate_connection() \ No newline at end of file diff --git a/NetSecGame/coordinator/__init__.py b/netsecgame/coordinator/__init__.py similarity index 100% rename from NetSecGame/coordinator/__init__.py rename to netsecgame/coordinator/__init__.py diff --git a/NetSecGame/coordinator/config_parser.py b/netsecgame/coordinator/config_parser.py similarity index 99% rename from NetSecGame/coordinator/config_parser.py rename to netsecgame/coordinator/config_parser.py index a3650d18..500dffcf 100644 --- a/NetSecGame/coordinator/config_parser.py +++ b/netsecgame/coordinator/config_parser.py @@ -5,7 +5,7 @@ import yaml # This is used so the agent can see the environment and game components import importlib -from NetSecGame.game_components import IP, Data, Network, Service +from netsecgame.game_components import IP, Data, Network, Service import netaddr import logging from random import randint diff --git a/NetSecGame/coordinator/coordinator.py b/netsecgame/coordinator/coordinator.py similarity index 99% rename from NetSecGame/coordinator/coordinator.py rename to netsecgame/coordinator/coordinator.py index 74d13d24..b296716e 100644 --- a/NetSecGame/coordinator/coordinator.py +++ b/netsecgame/coordinator/coordinator.py @@ -4,10 +4,10 @@ from datetime import datetime import signal -from NetSecGame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig -from NetSecGame.coordinator.global_defender import GlobalDefender -from NetSecGame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl -from NetSecGame.coordinator.config_parser import ConfigParser +from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig +from netsecgame.coordinator.global_defender import GlobalDefender +from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl +from netsecgame.coordinator.config_parser import ConfigParser import os from aiohttp import ClientSession from cyst.api.environment.environment import Environment diff --git a/NetSecGame/coordinator/global_defender.py b/netsecgame/coordinator/global_defender.py similarity index 98% rename from NetSecGame/coordinator/global_defender.py rename to netsecgame/coordinator/global_defender.py index fcb497f0..28cf3c0c 100644 --- a/NetSecGame/coordinator/global_defender.py +++ b/netsecgame/coordinator/global_defender.py @@ -1,6 +1,6 @@ # Author: Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz from itertools import groupby -from NetSecGame.game_components import ActionType, Action +from netsecgame.game_components import ActionType, Action from random import random diff --git a/NetSecGame/coordinator/netsecenv_conf.yaml b/netsecgame/coordinator/netsecenv_conf.yaml similarity index 100% rename from NetSecGame/coordinator/netsecenv_conf.yaml rename to netsecgame/coordinator/netsecenv_conf.yaml diff --git a/NetSecGame/coordinator/netsecevn_conf_cyst_integration.yaml b/netsecgame/coordinator/netsecevn_conf_cyst_integration.yaml similarity index 100% rename from NetSecGame/coordinator/netsecevn_conf_cyst_integration.yaml rename to netsecgame/coordinator/netsecevn_conf_cyst_integration.yaml diff --git a/NetSecGame/coordinator/scenarios/__init__.py b/netsecgame/coordinator/scenarios/__init__.py similarity index 100% rename from NetSecGame/coordinator/scenarios/__init__.py rename to netsecgame/coordinator/scenarios/__init__.py diff --git a/NetSecGame/coordinator/scenarios/one_net.py b/netsecgame/coordinator/scenarios/one_net.py similarity index 100% rename from NetSecGame/coordinator/scenarios/one_net.py rename to netsecgame/coordinator/scenarios/one_net.py diff --git a/NetSecGame/coordinator/scenarios/scenario_configuration.py b/netsecgame/coordinator/scenarios/scenario_configuration.py similarity index 100% rename from NetSecGame/coordinator/scenarios/scenario_configuration.py rename to netsecgame/coordinator/scenarios/scenario_configuration.py diff --git a/NetSecGame/coordinator/scenarios/smaller_scenario_configuration.py b/netsecgame/coordinator/scenarios/smaller_scenario_configuration.py similarity index 100% rename from NetSecGame/coordinator/scenarios/smaller_scenario_configuration.py rename to netsecgame/coordinator/scenarios/smaller_scenario_configuration.py diff --git a/NetSecGame/coordinator/scenarios/test_scenario_configuration.py b/netsecgame/coordinator/scenarios/test_scenario_configuration.py similarity index 100% rename from NetSecGame/coordinator/scenarios/test_scenario_configuration.py rename to netsecgame/coordinator/scenarios/test_scenario_configuration.py diff --git a/NetSecGame/coordinator/scenarios/three_net_scenario.py b/netsecgame/coordinator/scenarios/three_net_scenario.py similarity index 100% rename from NetSecGame/coordinator/scenarios/three_net_scenario.py rename to netsecgame/coordinator/scenarios/three_net_scenario.py diff --git a/NetSecGame/coordinator/scenarios/tiny_scenario_configuration.py b/netsecgame/coordinator/scenarios/tiny_scenario_configuration.py similarity index 100% rename from NetSecGame/coordinator/scenarios/tiny_scenario_configuration.py rename to netsecgame/coordinator/scenarios/tiny_scenario_configuration.py diff --git a/NetSecGame/coordinator/scenarios/two_nets.py b/netsecgame/coordinator/scenarios/two_nets.py similarity index 100% rename from NetSecGame/coordinator/scenarios/two_nets.py rename to netsecgame/coordinator/scenarios/two_nets.py diff --git a/NetSecGame/coordinator/scenarios/two_nets_small.py b/netsecgame/coordinator/scenarios/two_nets_small.py similarity index 100% rename from NetSecGame/coordinator/scenarios/two_nets_small.py rename to netsecgame/coordinator/scenarios/two_nets_small.py diff --git a/NetSecGame/coordinator/scenarios/two_nets_tiny.py b/netsecgame/coordinator/scenarios/two_nets_tiny.py similarity index 100% rename from NetSecGame/coordinator/scenarios/two_nets_tiny.py rename to netsecgame/coordinator/scenarios/two_nets_tiny.py diff --git a/NetSecGame/coordinator/worlds/CYSTCoordinator.py b/netsecgame/coordinator/worlds/CYSTCoordinator.py similarity index 98% rename from NetSecGame/coordinator/worlds/CYSTCoordinator.py rename to netsecgame/coordinator/worlds/CYSTCoordinator.py index a6e06164..67f18370 100644 --- a/NetSecGame/coordinator/worlds/CYSTCoordinator.py +++ b/netsecgame/coordinator/worlds/CYSTCoordinator.py @@ -8,11 +8,11 @@ import logging import argparse from pathlib import Path -from NetSecGame.game_components import GameState, Action, ActionType, IP, Service ,Network -from NetSecGame.coordinator.coordinator import GameCoordinator +from netsecgame.game_components import GameState, Action, ActionType, IP, Service ,Network +from netsecgame.coordinator.coordinator import GameCoordinator from cyst.api.configuration.network.node import NodeConfig -from NetSecGame.utils.utils import get_logging_level +from netsecgame.utils.utils import get_logging_level def get_starting_position_from_cyst_config(cyst_objects): """ diff --git a/NetSecGame/coordinator/worlds/NSEGameCoordinator.py b/netsecgame/coordinator/worlds/NSEGameCoordinator.py similarity index 99% rename from NetSecGame/coordinator/worlds/NSEGameCoordinator.py rename to netsecgame/coordinator/worlds/NSEGameCoordinator.py index 36f09e4b..7e7b0e71 100644 --- a/NetSecGame/coordinator/worlds/NSEGameCoordinator.py +++ b/netsecgame/coordinator/worlds/NSEGameCoordinator.py @@ -12,11 +12,11 @@ from typing import Iterable from collections import defaultdict -from NetSecGame.game_components import GameState, Action, ActionType, IP, Network, Data, Service -from NetSecGame.coordinator.coordinator import GameCoordinator +from netsecgame.game_components import GameState, Action, ActionType, IP, Network, Data, Service +from netsecgame.coordinator.coordinator import GameCoordinator from cyst.api.configuration import NodeConfig, RouterConfig, ConnectionConfig, ExploitConfig, FirewallPolicy -from NetSecGame.utils.utils import get_logging_level +from netsecgame.utils.utils import get_logging_level class NSGCoordinator(GameCoordinator): diff --git a/NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py b/netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py similarity index 97% rename from NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py rename to netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py index 543db5a2..e592e9d1 100644 --- a/NetSecGame/coordinator/worlds/NSGRealWorldCoordinator.py +++ b/netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py @@ -9,9 +9,9 @@ import os from pathlib import Path -from NetSecGame.utils.utils import get_logging_level -from NetSecGame.game_components import GameState, Action, ActionType, Service,IP -from NetSecGame.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.utils.utils import get_logging_level +from netsecgame.game_components import GameState, Action, ActionType, Service,IP +from netsecgame.worlds.NSEGameCoordinator import NSGCoordinator class NSERealWorldGameCoordinator(NSGCoordinator): diff --git a/NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py b/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py similarity index 97% rename from NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py rename to netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py index 7e89530a..65fffa33 100644 --- a/NetSecGame/coordinator/worlds/WhiteBoxNSGCoordinator.py +++ b/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py @@ -4,9 +4,9 @@ import os import json from pathlib import Path -from NetSecGame.utils.utils import get_logging_level -from NetSecGame.game_components import Action, ActionType -from NetSecGame.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.utils.utils import get_logging_level +from netsecgame.game_components import Action, ActionType +from netsecgame.worlds.NSEGameCoordinator import NSGCoordinator diff --git a/NetSecGame/coordinator/worlds/__init__.py b/netsecgame/coordinator/worlds/__init__.py similarity index 100% rename from NetSecGame/coordinator/worlds/__init__.py rename to netsecgame/coordinator/worlds/__init__.py diff --git a/NetSecGame/docs/Components.md b/netsecgame/docs/Components.md similarity index 100% rename from NetSecGame/docs/Components.md rename to netsecgame/docs/Components.md diff --git a/NetSecGame/docs/Coordinator.md b/netsecgame/docs/Coordinator.md similarity index 100% rename from NetSecGame/docs/Coordinator.md rename to netsecgame/docs/Coordinator.md diff --git a/NetSecGame/docs/Trajectory_analysis.md b/netsecgame/docs/Trajectory_analysis.md similarity index 100% rename from NetSecGame/docs/Trajectory_analysis.md rename to netsecgame/docs/Trajectory_analysis.md diff --git a/NetSecGame/docs/figures/architecture_diagram.jpg b/netsecgame/docs/figures/architecture_diagram.jpg similarity index 100% rename from NetSecGame/docs/figures/architecture_diagram.jpg rename to netsecgame/docs/figures/architecture_diagram.jpg diff --git a/NetSecGame/docs/figures/message_passing_coordinator.jpg b/netsecgame/docs/figures/message_passing_coordinator.jpg similarity index 100% rename from NetSecGame/docs/figures/message_passing_coordinator.jpg rename to netsecgame/docs/figures/message_passing_coordinator.jpg diff --git a/NetSecGame/docs/figures/scenarios/scenario 1_small.png b/netsecgame/docs/figures/scenarios/scenario 1_small.png similarity index 100% rename from NetSecGame/docs/figures/scenarios/scenario 1_small.png rename to netsecgame/docs/figures/scenarios/scenario 1_small.png diff --git a/NetSecGame/docs/figures/scenarios/scenario_1.png b/netsecgame/docs/figures/scenarios/scenario_1.png similarity index 100% rename from NetSecGame/docs/figures/scenarios/scenario_1.png rename to netsecgame/docs/figures/scenarios/scenario_1.png diff --git a/NetSecGame/docs/figures/scenarios/scenario_1_tiny.png b/netsecgame/docs/figures/scenarios/scenario_1_tiny.png similarity index 100% rename from NetSecGame/docs/figures/scenarios/scenario_1_tiny.png rename to netsecgame/docs/figures/scenarios/scenario_1_tiny.png diff --git a/NetSecGame/docs/figures/scenarios/three_nets.png b/netsecgame/docs/figures/scenarios/three_nets.png similarity index 100% rename from NetSecGame/docs/figures/scenarios/three_nets.png rename to netsecgame/docs/figures/scenarios/three_nets.png diff --git a/NetSecGame/game_components.py b/netsecgame/game_components.py similarity index 100% rename from NetSecGame/game_components.py rename to netsecgame/game_components.py diff --git a/NetSecGame/utils/__init__.py b/netsecgame/utils/__init__.py similarity index 100% rename from NetSecGame/utils/__init__.py rename to netsecgame/utils/__init__.py diff --git a/NetSecGame/utils/action_plots.r b/netsecgame/utils/action_plots.r similarity index 100% rename from NetSecGame/utils/action_plots.r rename to netsecgame/utils/action_plots.r diff --git a/NetSecGame/utils/action_plots_readme.md b/netsecgame/utils/action_plots_readme.md similarity index 100% rename from NetSecGame/utils/action_plots_readme.md rename to netsecgame/utils/action_plots_readme.md diff --git a/NetSecGame/utils/actions_parser.py b/netsecgame/utils/actions_parser.py similarity index 100% rename from NetSecGame/utils/actions_parser.py rename to netsecgame/utils/actions_parser.py diff --git a/NetSecGame/utils/aidojo_log_colorizer.py b/netsecgame/utils/aidojo_log_colorizer.py similarity index 100% rename from NetSecGame/utils/aidojo_log_colorizer.py rename to netsecgame/utils/aidojo_log_colorizer.py diff --git a/NetSecGame/utils/gamaplay_graphs.py b/netsecgame/utils/gamaplay_graphs.py similarity index 99% rename from NetSecGame/utils/gamaplay_graphs.py rename to netsecgame/utils/gamaplay_graphs.py index 13e71618..3f862bed 100644 --- a/NetSecGame/utils/gamaplay_graphs.py +++ b/netsecgame/utils/gamaplay_graphs.py @@ -5,7 +5,7 @@ import argparse import matplotlib.pyplot as plt -from NetSecGame.game_components import GameState, Action +from netsecgame.game_components import GameState, Action class TrajectoryGraph: def __init__(self)->None: diff --git a/NetSecGame/utils/log_parser.py b/netsecgame/utils/log_parser.py similarity index 100% rename from NetSecGame/utils/log_parser.py rename to netsecgame/utils/log_parser.py diff --git a/NetSecGame/utils/trajectory_analysis.py b/netsecgame/utils/trajectory_analysis.py similarity index 99% rename from NetSecGame/utils/trajectory_analysis.py rename to netsecgame/utils/trajectory_analysis.py index 99678aaf..673a1042 100644 --- a/NetSecGame/utils/trajectory_analysis.py +++ b/netsecgame/utils/trajectory_analysis.py @@ -9,7 +9,7 @@ import plotly.graph_objects as go from sklearn.preprocessing import StandardScaler -from NetSecGame.game_components import GameState, Action, ActionType +from netsecgame.game_components import GameState, Action, ActionType diff --git a/NetSecGame/utils/utils.py b/netsecgame/utils/utils.py similarity index 99% rename from NetSecGame/utils/utils.py rename to netsecgame/utils/utils.py index 846e3d19..87f0e6bf 100644 --- a/NetSecGame/utils/utils.py +++ b/netsecgame/utils/utils.py @@ -13,7 +13,7 @@ import jsonlines # --- Local Imports --- -from NetSecGame.game_components import ( +from netsecgame.game_components import ( Action, ActionType, Data, diff --git a/pyproject.toml b/pyproject.toml index de4d08b9..0b432492 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "NetSecGame" +name = "netsecgame" dynamic = ["version"] description = "A package for coordinating AI-driven network simulation games." readme = "README.md" @@ -58,7 +58,7 @@ server = [ ] dev = [ - "NetSecGame[server]", + "netsecgame[server]", "pytest", "ruff", "pytest-asyncio" @@ -72,7 +72,7 @@ docs = [ ] [tool.setuptools.dynamic] -version = { attr = "NetSecGame.__version__" } +version = { attr = "netsecgame.__version__" } [project.urls] Homepage = "https://github.com/stratosphereips/NetSecGame" From b4a4920b1566a8db461ad707ddc4bdf51c869a9d Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 15:39:11 +0100 Subject: [PATCH 023/112] rename NetSecGame to netsecgame to comply with PEP8 --- tests/OLD_test_actions.py | 4 ++-- tests/components/test_action.py | 2 +- tests/components/test_data.py | 2 +- tests/components/test_game_state.py | 2 +- tests/components/test_ip.py | 2 +- tests/components/test_network.py | 2 +- tests/components/test_service.py | 2 +- tests/coordinator/test_agent_server.py | 4 ++-- tests/coordinator/test_coordinator_core.py | 4 ++-- tests/coordinator/test_global_defender.py | 4 ++-- tests/manual/three_nets/manual_test_three_net_scenario.py | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/OLD_test_actions.py b/tests/OLD_test_actions.py index 8b8434a9..281b674d 100644 --- a/tests/OLD_test_actions.py +++ b/tests/OLD_test_actions.py @@ -5,8 +5,8 @@ import sys from os import path sys.path.append( path.dirname(path.dirname( path.abspath(__file__) ) )) -from NetSecGame.worlds.network_security_game import NetworkSecurityEnvironment -import NetSecGame.game_components as components +from netsecgame.worlds.network_security_game import NetworkSecurityEnvironment +import netsecgame.game_components as components import pytest # Fixture are used to hold the current state and the environment diff --git a/tests/components/test_action.py b/tests/components/test_action.py index f1af288a..c0840003 100644 --- a/tests/components/test_action.py +++ b/tests/components/test_action.py @@ -1,7 +1,7 @@ # Authors: Maria Rigaki - maria.rigaki@aic.fel.cvut.cz # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import json -from NetSecGame.game_components import Action, ActionType, IP, Network, Data, Service, AgentInfo +from netsecgame.game_components import Action, ActionType, IP, Network, Data, Service, AgentInfo class TestComponentActionType: """ diff --git a/tests/components/test_data.py b/tests/components/test_data.py index 9882053a..2ed0498b 100644 --- a/tests/components/test_data.py +++ b/tests/components/test_data.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from NetSecGame.game_components import Data +from netsecgame.game_components import Data @pytest.fixture def sample_data_minimal(): diff --git a/tests/components/test_game_state.py b/tests/components/test_game_state.py index 726fcc2c..2f92bdb9 100644 --- a/tests/components/test_game_state.py +++ b/tests/components/test_game_state.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import json import pytest -from NetSecGame.game_components import GameState, IP, Network, Data, Service +from netsecgame.game_components import GameState, IP, Network, Data, Service # pytest fixtures for creating sample objects @pytest.fixture diff --git a/tests/components/test_ip.py b/tests/components/test_ip.py index b7a60cb4..df938357 100644 --- a/tests/components/test_ip.py +++ b/tests/components/test_ip.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from NetSecGame.game_components import IP +from netsecgame.game_components import IP # Pytest fixtures for creating sample IP objects @pytest.fixture diff --git a/tests/components/test_network.py b/tests/components/test_network.py index 22657406..d11f654a 100644 --- a/tests/components/test_network.py +++ b/tests/components/test_network.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from NetSecGame.game_components import Network +from netsecgame.game_components import Network # Pytest fixture for creating a sample Network object @pytest.fixture diff --git a/tests/components/test_service.py b/tests/components/test_service.py index fc4164c4..2c3cdd2d 100644 --- a/tests/components/test_service.py +++ b/tests/components/test_service.py @@ -2,7 +2,7 @@ # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import pytest import dataclasses -from NetSecGame.game_components import Service +from netsecgame.game_components import Service # Fixtures for Service objects @pytest.fixture diff --git a/tests/coordinator/test_agent_server.py b/tests/coordinator/test_agent_server.py index 4b71622a..643641ca 100644 --- a/tests/coordinator/test_agent_server.py +++ b/tests/coordinator/test_agent_server.py @@ -3,8 +3,8 @@ import pytest from unittest.mock import AsyncMock, MagicMock from contextlib import suppress -from NetSecGame.coordinator.coordinator import AgentServer -from NetSecGame.game_components import Action, ActionType, ProtocolConfig +from netsecgame.coordinator.coordinator import AgentServer +from netsecgame.game_components import Action, ActionType, ProtocolConfig # ----------------------- # Fixtures diff --git a/tests/coordinator/test_coordinator_core.py b/tests/coordinator/test_coordinator_core.py index ba1222cf..0a5dee15 100644 --- a/tests/coordinator/test_coordinator_core.py +++ b/tests/coordinator/test_coordinator_core.py @@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, patch from types import SimpleNamespace -from NetSecGame.coordinator.coordinator import GameCoordinator -from NetSecGame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus +from netsecgame.coordinator.coordinator import GameCoordinator +from netsecgame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus # ----------------------- # Fixtures diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index 65db243c..b3b53c37 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -1,6 +1,6 @@ import pytest -from NetSecGame.game_components import ActionType, Action -from NetSecGame.coordinator.global_defender import GlobalDefender +from netsecgame.game_components import ActionType, Action +from netsecgame.coordinator.global_defender import GlobalDefender from unittest.mock import patch @pytest.fixture diff --git a/tests/manual/three_nets/manual_test_three_net_scenario.py b/tests/manual/three_nets/manual_test_three_net_scenario.py index 7082d67d..114495fb 100644 --- a/tests/manual/three_nets/manual_test_three_net_scenario.py +++ b/tests/manual/three_nets/manual_test_three_net_scenario.py @@ -5,7 +5,7 @@ PATH = path.dirname( path.dirname( path.dirname( path.dirname( path.abspath(__file__) ) ) )) sys.path.append(path.dirname( path.dirname( path.dirname( path.dirname( path.abspath(__file__) ) ) ))) from NetSecGameAgents.agents import base_agent -from NetSecGame.game_components import Action, ActionType, IP, Network, Service, Data +from netsecgame.game_components import Action, ActionType, IP, Network, Service, Data if __name__ == "__main__": From 73734863ec0afa360d379b41b5de5ffb8343b772 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 15:40:21 +0100 Subject: [PATCH 024/112] fic path --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0b432492..b682298d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ Issues = "https://github.com/stratosphereips/NetSecGame/issues" [tool.setuptools.packages.find] where = ["."] -include = ["NetSecGame*"] +include = ["netsecgame*"] exclude = [ "tests*", "notebooks*", From 98e67f4ff9d5e9b91218b4834a43f45ae6ab4d77 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 19:24:27 +0100 Subject: [PATCH 025/112] Update dockerinfo --- .dockerignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index b46d4cbc..f17bf4d6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,14 +5,17 @@ .pytest_cache .ruff_cache .vscode +__pycache__ +build/ dist/ docs/ examples/ figures/ logs/ -mlruns/ +netsecgame.egg-info/ notebooks/ NetSecGameAgents/ +site/ tests/ trajectories/ readme_images/ From 77837b543282dbc710056a0800756533f28dc8fa Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 19:27:08 +0100 Subject: [PATCH 026/112] Update docker file to match repo structure and pyproject --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d7f0f6a4..7f623928 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,13 +20,13 @@ WORKDIR ${DESTINATION_DIR} # Install any necessary Python dependencies # If a requirements.txt file is in the repository -RUN if [ -f pyproject.toml ]; then pip install . ; fi +RUN if [ -f pyproject.toml ]; then pip install .[server] ; fi # Expose the port the coordinator will run on EXPOSE 9000 # Run the Python script when the container launches (with default arguments --task_config=netsecenv_conf.yaml --game_port=9000 --game_host=0.0.0.0) -ENTRYPOINT ["python3", "-m", "AIDojoCoordinator.worlds.NSEGameCoordinator", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] +ENTRYPOINT ["python3", "-m", "netsecgame.coordinator.worlds.NSEGameCoordinator", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] # Default command arguments (can be overridden at runtime) CMD ["--debug_level=INFO"] From 52af7dec4147d5ce9f35438f405c1d63ed3aab32 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sun, 18 Jan 2026 19:45:04 +0100 Subject: [PATCH 027/112] Add valid action generation to the package --- netsecgame/__init__.py | 4 ++- netsecgame/utils/utils.py | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index f3d11383..eff8b6ee 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -28,7 +28,8 @@ store_trajectories_to_jsonl, read_trajectories_from_jsonl, observation_as_dict, - observation_to_str + observation_to_str, + generate_valid_actions ) # Define the public API of the package @@ -56,4 +57,5 @@ "read_trajectories_from_jsonl", "observation_as_dict", "observation_to_str", + "generate_valid_actions" ] \ No newline at end of file diff --git a/netsecgame/utils/utils.py b/netsecgame/utils/utils.py index 87f0e6bf..db0c94ff 100644 --- a/netsecgame/utils/utils.py +++ b/netsecgame/utils/utils.py @@ -192,6 +192,61 @@ def read_trajectories_from_jsonl(filepath:str)->list: """ raise NotImplementedError("This function is not yet implemented.") +def generate_valid_actions(state: GameState, include_blocks=False)->list: + """Function that generates a list of all valid actions in a given GameState + Args: + state (GameState): The current game state. + include_blocks (bool): Whether to include BlockIP actions. Defaults to False. + Returns: + list: A list of valid Action objects. + """ + valid_actions = set() + def is_fw_blocked(state, src_ip, dst_ip)->bool: + blocked = False + try: + blocked = dst_ip in state.known_blocks[src_ip] + except KeyError: + pass #this src ip has no known blocks + return blocked + + for source_host in state.controlled_hosts: + #Network Scans + for network in state.known_networks: + # TODO ADD neighbouring networks + valid_actions.add(Action(ActionType.ScanNetwork, parameters={"target_network": network, "source_host": source_host,})) + + # Service Scans + for blocked_host in state.known_hosts: + if not is_fw_blocked(state, source_host, blocked_host): + valid_actions.add(Action(ActionType.FindServices, parameters={"target_host": blocked_host, "source_host": source_host,})) + + # Service Exploits + for blocked_host, service_list in state.known_services.items(): + if not is_fw_blocked(state, source_host,blocked_host): + for service in service_list: + valid_actions.add(Action(ActionType.ExploitService, parameters={"target_host": blocked_host,"target_service": service,"source_host": source_host,})) + # Data Scans + for blocked_host in state.controlled_hosts: + if not is_fw_blocked(state, source_host,blocked_host): + valid_actions.add(Action(ActionType.FindData, parameters={"target_host": blocked_host, "source_host": blocked_host})) + + # Data Exfiltration + for source_host, data_list in state.known_data.items(): + for data in data_list: + for trg_host in state.controlled_hosts: + if trg_host != source_host: + if not is_fw_blocked(state, source_host,trg_host): + valid_actions.add(Action(ActionType.ExfiltrateData, parameters={"target_host": trg_host, "source_host": source_host, "data": data})) + + # BlockIP + if include_blocks: + for source_host in state.controlled_hosts: + for target_host in state.controlled_hosts: + if not is_fw_blocked(state, source_host,target_host): + for blocked_ip in state.known_hosts: + valid_actions.add(Action(ActionType.BlockIP, {"target_host":target_host, "source_host":source_host, "blocked_host":blocked_ip})) + return list(valid_actions) + if __name__ == "__main__": state = GameState(known_networks={Network("1.1.1.1", 24),Network("1.1.1.2", 24)}, known_hosts={IP("192.168.1.2"), IP("192.168.1.3")}, controlled_hosts={IP("192.168.1.2")}, From 3b208f2ea2367fc4cad8f540415816a2b5a00690 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 10:29:12 +0100 Subject: [PATCH 028/112] Simplification of readme, better clarity --- README.md | 114 +++++++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index c94210e5..7a0ef933 100755 --- a/README.md +++ b/README.md @@ -8,42 +8,47 @@ The NetSecGame (Network Security Game) is a framework for training and evaluation of AI agents in network security tasks (both offensive and defensive). It is built with [CYST](https://pypi.org/project/cyst/) network simulator and enables rapid development and testing of AI agents in highly configurable scenarios. Examples of implemented agents can be seen in the submodule [NetSecGameAgents](https://github.com/stratosphereips/NetSecGameAgents/tree/main). ## Installation Guide -It is recommended to install the NetSecGame in a virtual environment: -### Python venv -1. +It is recommended to run the environment in the Docker container. The up-to-date image can be found in [Dockerhub](https://hub.docker.com/r/stratosphereips/netsecgame). +```bash +docker pull stratosphereips/netsecgame +``` +#### Building the image locally +Optionally, you can build the image locally with: +```bash +docker build -t netsecgame:local . +``` + +### Installing from source +In case you need to modify the envirment and run directly, we recommed to insall it in a virtual environemnt (Python vevn or Conda): +#### Python venv +1. Create new virtual environment ```bash python -m venv ``` -2. +2. Activate newly created venv ```bash source /bin/activate ``` -### Conda -1. +#### Conda +1. Create new conda environment ```bash conda create --name aidojo python==3.12 ``` -2. +2. Activate newly created conda env ```bash conda activate aidojo ``` -After the virtual environment is activated, install using pip: +### After preparing virutual environment, install using pip: ```bash pip install -e . ``` -### With Docker -The NetSecGame can be run in a Docker container. You can build the image locally with: -```bash -docker build -t aidojo-nsg-coordinator:latest . -``` -or use the available image from [Dockerhub](https://hub.docker.com/r/stratosphereips/netsecgame). -```bash -docker pull stratosphereips/netsecgame -``` + ## Quick Start -A task configuration needs to be specified to start the NetSecGame (see [Configuration](configuration.md)). For the first step, the example task configuration is recommended: +A task configuration YAML file is required for starting the NetSecGame environment. For the first step, the example task configuration is recommended: + +### Example Configuration ```yaml # Example of the task configuration for NetSecGame # The objective of the Attacker in this task is to locate specific data @@ -51,11 +56,11 @@ A task configuration needs to be specified to start the NetSecGame (see [Configu # The scenario starts AFTER the initial breach of the local network # (the attacker controls 1 local device + the remote C&C server). -coordinator: - agents: +coordinator: + agents: Attacker: # Configuration of 'Attacker' agents - max_steps: 25 - goal: + max_steps: 25 # timout set for the role `Attacker` + goal: # Definition of the goal state description: "Exfiltrate data from Samba server to remote C&C server." is_any_part_of_goal_random: True known_networks: [] @@ -64,10 +69,10 @@ coordinator: known_services: {} known_data: {213.47.23.195: [[User1,DataFromServer1]]} # winning condition known_blocks: {} - start_position: # Defined starting position of the attacker + start_position: # Definition of the starting state (keywords "random" and "all" can be used) known_networks: [] known_hosts: [] - controlled_hosts: [213.47.23.195, random] # + controlled_hosts: [213.47.23.195, random] # keyword 'random' will be replaced by randomly selected IP during initilization known_services: {} known_data: {} known_blocks: {} @@ -92,58 +97,63 @@ coordinator: blocked_ips: {} known_blocks: {} -env: +env: # Environment configuraion scenario: 'two_networks_tiny' # use the smallest topology for this example use_global_defender: False # Do not use global SIEM Defender use_dynamic_addresses: False # Do not randomize IP addresses use_firewall: True # Use firewall save_trajectories: False # Do not store trajectories - required_players: 1 + required_players: 1 # Minimal amount of agents requiered to start the game rewards: # Configurable reward function success: 100 step: -1 fail: -10 false_positive: -5 ``` - -The game can be started with: -```bash -python3 -m AIDojoCoordinator.worlds.NSEGameCoordinator \ - --task_config=./examples/example_config.yaml \ - --game_port=9000 -``` -Upon which the game server is created on `localhost:9000` to which the agents can connect to interact in the NetSecGame. - -### Docker Container -When running in the Docker container, the NetSecGame can be started with: +### Starting the NetSecGame +With the configuration ready the environment can be started in selected port +#### In Docker container ```bash -docker run -it --rm \ +docker run -d --rm --name nsg-server\ -v $(pwd)/examples/example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml \ -v $(pwd)/logs:/netsecgame/logs \ -p 9000:9000 stratosphereips/netsecgame + --debug_level="INFO" ``` -optionally, you can set the logging level with `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): +`--name nsg-server`: specifies the name of the container -```bash -docker run -it --rm \ - -v $(pwd)/examples/example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml \ - -v $(pwd)/logs:/netsecgame/logs \ - -p 9000:9000 stratosphereips/netsecgame:latest \ - --debug_level="WARNING" -``` +`-v :/netsecgame/netsecenv_conf.yaml` : Mapping of the configuration file + +`-v $(pwd)/logs:/netsecgame/logs`: Mapping of the folder where logs are stored -#### Running on Windows (with Docker Desktop): +` -p :9000`: Mapping of the port in which the server runs + +`--debug_level` is an optional parameter to control the logging level `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): +##### Running on Windows (with Docker desktop) +When running on Windows, Docker desktop is required. T ```cmd docker run -d --rm --name netsecgame-server ^ -p 9000:9000 ^ -v "%cd%\examples\example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml" ^ -v "%cd%\logs:/netsecgame/logs" ^ stratosphereips/netsecgame:latest + --debug_level="INFO" +``` + +#### Locally +The environment can be started locally with from the root folder of the repository with following command: +```bash +python3 -m netsecgame.worlds.NSEGameCoordinator \ + --task_config=./examples/example_config.yaml \ + --game_port=9000 + --debug_level="INFO" ``` +Upon which the game server is created on `localhost:9000` to which the agents can connect to interact in the NetSecGame. ## Documentation You can find user documentation at [https://stratosphereips.github.io/NetSecGame/](https://stratosphereips.github.io/NetSecGame/) -## Components of the NetSecGame Environment + +### Components of the NetSecGame Environment The architecture of the environment can be seen [here](docs/Architecture.md). The NetSecGame environment has several components in the following files: ``` @@ -170,18 +180,18 @@ The NetSecGame environment has several components in the following files: | ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) | ├── global_defender.py # Stochastic (non-agentic defender) ``` -### Directory Details +#### Directory Details - `coordinator.py`: Basic coordinator class. Handles agent communication and coordination. **Does not implement dynamics of the world** and must be extended (see examples in `worlds/`). - `game_components.py`: Implements a library with objects used in the environment. See [detailed explanation](AIDojoCoordinator/docs/Components.md) of the game components. - `global_defender.py`: Implements a global (omnipresent) defender that can be used to stop agents. Simulation of SIEM. -#### **`worlds/`** +##### **`worlds/`** Modules for different world configurations: - `NSGCoordinator.py`: Coordinator for the Network Security Game. - `NSGRealWorldCoordinator.py`: Real-world NSG coordinator (actions are executed in the *real network*). - `CYSTCoordinator.py`: Coordinator for CYST-based simulations (requires CYST running). -#### **`scenarios/`** +##### **`scenarios/`** Predefined scenario configurations: - `tiny_scenario_configuration.py`: A minimal example scenario. - `smaller_scenario_configuration.py`: A compact scenario configuration used for development and rapid testing. @@ -189,7 +199,7 @@ Predefined scenario configurations: - `three_net_configuration.py`: Configuration for a three-network scenario. Used for the evaluation of the model overfitting. Implements the network game's configuration of hosts, data, services, and connections. It is taken from [CYST](https://pypi.org/project/cyst/). -#### **`utils/`** +##### **`utils/`** Helper modules: - `utils.py`: General-purpose utilities. - `log_parser.py`: Tools for parsing game logs. From 151b6910fb72229745c923341b93748244d99f01 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 10:41:20 +0100 Subject: [PATCH 029/112] Fix typing --- .../coordinator/worlds/WhiteBoxNSGCoordinator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py b/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py index 65fffa33..6e360ad1 100644 --- a/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py +++ b/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py @@ -1,3 +1,4 @@ +# Author: Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import itertools import argparse import logging @@ -6,9 +7,7 @@ from pathlib import Path from netsecgame.utils.utils import get_logging_level from netsecgame.game_components import Action, ActionType -from netsecgame.worlds.NSEGameCoordinator import NSGCoordinator - - +from netsecgame.coordinator.worlds.NSEGameCoordinator import NSGCoordinator class WhiteBoxNSGCoordinator(NSGCoordinator): @@ -26,15 +25,17 @@ def _initialize(self): super()._initialize() # All components are initialized, now we can set the action mapping self.logger.debug("Creating action mapping for the game.") - self._generate_all_actions() + self._all_actions = self._generate_all_actions() self._registration_info = { "all_actions": json.dumps([v.as_dict for v in self._all_actions]), } if self._all_actions is not None else {} - def _generate_all_actions(self)-> list: + def _generate_all_actions(self)-> list[Action]: """ Generate a list of all possible actions for the game. + Returns: + list[Action]: List of all possible actions. """ actions = [] all_ips = [self._ip_mapping[ip] for ip in self._ip_to_hostname.keys()] @@ -119,7 +120,7 @@ def _generate_all_actions(self)-> list: self.logger.info(f"Created action mapping with {len(actions)} actions.") for action in actions: self.logger.debug(action) - self._all_actions = actions + return actions def _create_state_from_view(self, view, add_neighboring_nets = True): From 409f2a53bc8d1eb440cab0aab665b95cfd76c815 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 11:00:21 +0100 Subject: [PATCH 030/112] Fix the path for imports --- netsecgame/coordinator/config_parser.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/netsecgame/coordinator/config_parser.py b/netsecgame/coordinator/config_parser.py index 500dffcf..5d662f4c 100644 --- a/netsecgame/coordinator/config_parser.py +++ b/netsecgame/coordinator/config_parser.py @@ -354,14 +354,14 @@ def get_scenario(self): Get the scenario config objects based on the configuration. Only import objects that are selected via importlib. """ allowed_names = { - "scenario1" : "NetSecGame.coordinator.scenarios.scenario_configuration", - "scenario1_small" : "NetSecGame.coordinator.scenarios.smaller_scenario_configuration", - "scenario1_tiny" : "NetSecGame.coordinator.scenarios.tiny_scenario_configuration", - "one_network": "NetSecGame.coordinator.scenarios.one_net", - "three_net_scenario": "NetSecGame.coordinator.scenarios.three_net_scenario", - "two_networks": "NetSecGame.coordinator.scenarios.two_nets", # same as scenario1 - "two_networks_small": "NetSecGame.coordinator.scenarios.two_nets_small", # same as scenario1_small - "two_networks_tiny": "NetSecGame.coordinator.scenarios.two_nets_tiny", # same as scenario1_small + "scenario1" : "netsecgame.coordinator.scenarios.scenario_configuration", + "scenario1_small" : "netsecgame.coordinator.scenarios.smaller_scenario_configuration", + "scenario1_tiny" : "netsecgame.coordinator.scenarios.tiny_scenario_configuration", + "one_network": "netsecgame.coordinator.scenarios.one_net", + "three_net_scenario": "netsecgame.coordinator.scenarios.three_net_scenario", + "two_networks": "netsecgame.coordinator.scenarios.two_nets", # same as scenario1 + "two_networks_small": "netsecgame.coordinator.scenarios.two_nets_small", # same as scenario1_small + "two_networks_tiny": "netsecgame.coordinator.scenarios.two_nets_tiny", # same as scenario1_small } scenario_name = self.config['env']['scenario'] From 7f99fdb9c9feaa55996e6957e0fc21cf2fd46fd6 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 11:14:59 +0100 Subject: [PATCH 031/112] Fix path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a0ef933..1ee3d4ff 100755 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ docker run -d --rm --name netsecgame-server ^ The environment can be started locally with from the root folder of the repository with following command: ```bash python3 -m netsecgame.worlds.NSEGameCoordinator \ - --task_config=./examples/example_config.yaml \ + --task_config=./examples/example_task_configuration.yaml \ --game_port=9000 --debug_level="INFO" ``` From c11c63e5e15a844f0fcc563bccfdc1a636f72cbd Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 17:57:28 +0100 Subject: [PATCH 032/112] Add correct path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ee3d4ff..a77dae6e 100755 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ docker run -d --rm --name netsecgame-server ^ #### Locally The environment can be started locally with from the root folder of the repository with following command: ```bash -python3 -m netsecgame.worlds.NSEGameCoordinator \ +python3 -m netsecgame.coordinator.worlds.NSEGameCoordinator \ --task_config=./examples/example_task_configuration.yaml \ --game_port=9000 --debug_level="INFO" From 66632e9c804e81a3c4538f6b16357a5c490ce846 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Tue, 20 Jan 2026 18:16:25 +0100 Subject: [PATCH 033/112] Better organize the agents --- netsecgame/agents/__init__.py | 0 netsecgame/{ => agents}/base_agent.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 netsecgame/agents/__init__.py rename netsecgame/{ => agents}/base_agent.py (100%) diff --git a/netsecgame/agents/__init__.py b/netsecgame/agents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/netsecgame/base_agent.py b/netsecgame/agents/base_agent.py similarity index 100% rename from netsecgame/base_agent.py rename to netsecgame/agents/base_agent.py From def7e06c4898274e0310cb70b30e34b0a67c82b9 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:27:02 +0100 Subject: [PATCH 034/112] Fix encoding --- netsecgame/utils/utils.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/netsecgame/utils/utils.py b/netsecgame/utils/utils.py index db0c94ff..c1d3ba35 100644 --- a/netsecgame/utils/utils.py +++ b/netsecgame/utils/utils.py @@ -104,36 +104,32 @@ def state_as_ordered_string(state:GameState)->str: ret += "}" return ret -def observation_to_str(observation:Observation)-> str: +def observation_as_dict(observation: Observation) -> dict: """ - Generates JSON string representation of a given Observation object. + Generates dict representation of a given Observation object. + Acts as the single source of truth for the structure. """ - state_str = observation.state.as_json() - observation_dict = { - 'state': state_str, + return { + 'state': observation.state.as_dict, 'reward': observation.reward, 'end': observation.end, - 'info': dict(observation.info) + # Using dict() ensures safety if info is a namedtuple or other mapping + 'info': dict(observation.info) } + +def observation_to_str(observation: Observation) -> str: + """ + Generates JSON string representation of a given Observation object. + Relies on observation_as_dict to define the structure. + """ try: - observation_str = json.dumps(observation_dict) - return observation_str + # Clean JSON structure: {"state": {...}, "reward": 0, ...} + # No more escaped JSON strings inside the JSON. + return json.dumps(observation_as_dict(observation)) except Exception as e: print(f"Error in encoding observation '{observation}' to JSON string: {e}") raise e -def observation_as_dict(observation:Observation)->dict: - """ - Generates dict string representation of a given Observation object. - """ - observation_dict = { - 'state': observation.state.as_dict, - 'reward': observation.reward, - 'end': observation.end, - 'info': observation.info - } - return observation_dict - def parse_log_content(log_content:str)->Optional[list]: try: logs = [] From 0553f6ef81309f96b89d72f3b156196f0d11e426 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:36:14 +0100 Subject: [PATCH 035/112] fix path --- netsecgame/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index eff8b6ee..d28d663b 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -19,7 +19,7 @@ Service ) # Base agent -from .base_agent import BaseAgent +from .agents.base_agent import BaseAgent # Selected util functions from .utils.utils import ( From 109481097d0d5b46d83c31d8924c6c60dcd418c7 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:36:21 +0100 Subject: [PATCH 036/112] add imports --- netsecgame/utils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netsecgame/utils/__init__.py b/netsecgame/utils/__init__.py index e69de29b..e8a62729 100644 --- a/netsecgame/utils/__init__.py +++ b/netsecgame/utils/__init__.py @@ -0,0 +1,2 @@ +# all of the utility functions from utils module are accessible from the top-level netsecgame package +from . import utils \ No newline at end of file From e48ffd1ed26e9ee3a3ddf596c77b96603995b5f9 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:44:20 +0100 Subject: [PATCH 037/112] Added deserialization methods --- netsecgame/utils/utils.py | 63 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/netsecgame/utils/utils.py b/netsecgame/utils/utils.py index c1d3ba35..98f3f772 100644 --- a/netsecgame/utils/utils.py +++ b/netsecgame/utils/utils.py @@ -130,6 +130,58 @@ def observation_to_str(observation: Observation) -> str: print(f"Error in encoding observation '{observation}' to JSON string: {e}") raise e +def observation_from_dict(data: dict) -> Observation: + """ + Reconstructs an Observation object from a dictionary representation. + + Args: + data (dict): The dictionary containing observation data. + + Returns: + Observation: The reconstructed Observation namedtuple. + """ + try: + # Since we refactored serialization, 'state' is now a dictionary + state_data = data.get("state") + + # Robustness check: Ensure we have a dict before converting + if isinstance(state_data, dict): + state = GameState.from_dict(state_data) + else: + raise ValueError(f"Expected dictionary for 'state', got {type(state_data)}") + + return Observation( + state=state, + reward=float(data.get("reward", 0.0)), + end=bool(data.get("end", False)), + info=data.get("info", {}) + ) + except Exception as e: + print(f"Error in creating Observation from dict: {e}") + raise e + +def observation_from_str(json_str: str) -> Observation: + """ + Reconstructs an Observation object from a JSON string representation. + + Args: + json_str (str): The JSON string representation of the observation. + + Returns: + Observation: The reconstructed Observation namedtuple. + """ + try: + # 1. Parse the main JSON string -> returns a dict + data = json.loads(json_str) + + # 2. Pass that dict to our existing from_dict method + # This keeps the logic DRY (Don't Repeat Yourself) + return observation_from_dict(data) + + except Exception as e: + print(f"Error in creating Observation from string: {e}") + raise e + def parse_log_content(log_content:str)->Optional[list]: try: logs = [] @@ -250,4 +302,13 @@ def is_fw_blocked(state, src_ip, dst_ip)->bool: known_data={IP("192.168.1.3"):{Data("ChuckNorris", "data1"), Data("ChuckNorris", "data2")}, IP("192.168.1.2"):{Data("McGiver", "data2")}}) - print(state_as_ordered_string(state)) \ No newline at end of file + print(state_as_ordered_string(state)) + obs = Observation(state=state, reward=10.0, end=False, info={"info1":"value1"}) + obs_str = observation_to_str(obs) + print(obs_str) + obs_restored = observation_from_str(obs_str) + print(obs_restored) + print(observation_as_dict(obs_restored)) + actions = generate_valid_actions(state, include_blocks=True) + for action in actions: + print(action) \ No newline at end of file From 1db1f113c32224da4a32316c9a0810a2e906f4bf Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:45:20 +0100 Subject: [PATCH 038/112] Add de-serialization methods to the core package --- netsecgame/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index d28d663b..07a1efd9 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -29,6 +29,8 @@ read_trajectories_from_jsonl, observation_as_dict, observation_to_str, + observation_from_str, + observation_from_dict, generate_valid_actions ) @@ -57,5 +59,7 @@ "read_trajectories_from_jsonl", "observation_as_dict", "observation_to_str", + "observation_from_str", + "observation_from_dict", "generate_valid_actions" ] \ No newline at end of file From 201f832271f07565da711928e7010c41832a6e8f Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 10:59:05 +0100 Subject: [PATCH 039/112] Add twine to the dev dependencies --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b682298d..53b89367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,8 @@ dev = [ "netsecgame[server]", "pytest", "ruff", - "pytest-asyncio" + "pytest-asyncio", + "twine" ] docs = [ From b79ee5263cb7ce27363cec2221132ab03485603c Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:20:41 +0100 Subject: [PATCH 040/112] Fix netaddr version --- pyproject.toml | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 53b89367..d0a8c9cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ authors = [ # light-weight version (allows running and development of agents) dependencies = [ "jsonlines>=4.0.0", - "netaddr>=0.9.0", + "netaddr==0.9.0", ] requires-python = ">=3.12" @@ -25,36 +25,12 @@ requires-python = ">=3.12" # dependencies allowing to run the game server and the simulation server = [ - "aiohttp==3.11.8", - "attrs==23.2.0", - "beartype==0.19.0", - "cachetools==5.5.0", - "casefy==0.1.7", - "cyst==0.3.4", - "dictionaries==0.0.2", - "Faker==23.2.1", - "Jinja2==3.1.4", - "jsonlines==4.0.0", - "jsonpickle==3.3.0", - "kaleido==0.2.1", - "MarkupSafe==3.0.2", - "matplotlib==3.9.1", - "netaddr==0.9.0", - "networkx==3.4.2", - "numpy==1.26.4", - "pandas==2.2.2", - "plotly==5.22.0", - "pyserde==0.21.0", - "python-dateutil==2.8.2", - "PyYAML==6.0.1", - "redis==3.5.3", - "requests==2.32.3", - "scikit-learn==1.5.1", - "scipy==1.14.0", - "tenacity==8.5.0", - "typing-inspect==0.9.0", - "typing_extensions==4.12.2", - "cyst-core>=0.5.0" + "aiohttp>=3.11", + "cyst-core>=0.5.0", + "Faker>=23.2", + "numpy>=1.26", + "PyYAML>=6.0", + "requests>=2.32" ] dev = [ From 7af71d0bcb683b23199524e516f8620e30fa5b51 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:31:14 +0100 Subject: [PATCH 041/112] rename folder --- netsecgame/{coordinator => game}/__init__.py | 0 .../{coordinator => game}/config_parser.py | 16 ++++++++-------- netsecgame/{coordinator => game}/coordinator.py | 4 ++-- .../{coordinator => game}/global_defender.py | 0 .../{coordinator => game}/netsecenv_conf.yaml | 0 .../netsecevn_conf_cyst_integration.yaml | 0 .../{coordinator => game}/scenarios/__init__.py | 0 .../{coordinator => game}/scenarios/one_net.py | 0 .../scenarios/scenario_configuration.py | 0 .../scenarios/smaller_scenario_configuration.py | 0 .../scenarios/test_scenario_configuration.py | 0 .../scenarios/three_net_scenario.py | 0 .../scenarios/tiny_scenario_configuration.py | 0 .../{coordinator => game}/scenarios/two_nets.py | 0 .../scenarios/two_nets_small.py | 0 .../scenarios/two_nets_tiny.py | 0 .../worlds/CYSTCoordinator.py | 2 +- .../worlds/NSEGameCoordinator.py | 2 +- .../worlds/NSGRealWorldCoordinator.py | 2 +- .../worlds/WhiteBoxNSGCoordinator.py | 2 +- .../{coordinator => game}/worlds/__init__.py | 0 21 files changed, 14 insertions(+), 14 deletions(-) rename netsecgame/{coordinator => game}/__init__.py (100%) rename netsecgame/{coordinator => game}/config_parser.py (95%) rename netsecgame/{coordinator => game}/coordinator.py (99%) rename netsecgame/{coordinator => game}/global_defender.py (100%) rename netsecgame/{coordinator => game}/netsecenv_conf.yaml (100%) rename netsecgame/{coordinator => game}/netsecevn_conf_cyst_integration.yaml (100%) rename netsecgame/{coordinator => game}/scenarios/__init__.py (100%) rename netsecgame/{coordinator => game}/scenarios/one_net.py (100%) rename netsecgame/{coordinator => game}/scenarios/scenario_configuration.py (100%) rename netsecgame/{coordinator => game}/scenarios/smaller_scenario_configuration.py (100%) rename netsecgame/{coordinator => game}/scenarios/test_scenario_configuration.py (100%) rename netsecgame/{coordinator => game}/scenarios/three_net_scenario.py (100%) rename netsecgame/{coordinator => game}/scenarios/tiny_scenario_configuration.py (100%) rename netsecgame/{coordinator => game}/scenarios/two_nets.py (100%) rename netsecgame/{coordinator => game}/scenarios/two_nets_small.py (100%) rename netsecgame/{coordinator => game}/scenarios/two_nets_tiny.py (100%) rename netsecgame/{coordinator => game}/worlds/CYSTCoordinator.py (99%) rename netsecgame/{coordinator => game}/worlds/NSEGameCoordinator.py (99%) rename netsecgame/{coordinator => game}/worlds/NSGRealWorldCoordinator.py (99%) rename netsecgame/{coordinator => game}/worlds/WhiteBoxNSGCoordinator.py (98%) rename netsecgame/{coordinator => game}/worlds/__init__.py (100%) diff --git a/netsecgame/coordinator/__init__.py b/netsecgame/game/__init__.py similarity index 100% rename from netsecgame/coordinator/__init__.py rename to netsecgame/game/__init__.py diff --git a/netsecgame/coordinator/config_parser.py b/netsecgame/game/config_parser.py similarity index 95% rename from netsecgame/coordinator/config_parser.py rename to netsecgame/game/config_parser.py index 5d662f4c..5de253b5 100644 --- a/netsecgame/coordinator/config_parser.py +++ b/netsecgame/game/config_parser.py @@ -354,14 +354,14 @@ def get_scenario(self): Get the scenario config objects based on the configuration. Only import objects that are selected via importlib. """ allowed_names = { - "scenario1" : "netsecgame.coordinator.scenarios.scenario_configuration", - "scenario1_small" : "netsecgame.coordinator.scenarios.smaller_scenario_configuration", - "scenario1_tiny" : "netsecgame.coordinator.scenarios.tiny_scenario_configuration", - "one_network": "netsecgame.coordinator.scenarios.one_net", - "three_net_scenario": "netsecgame.coordinator.scenarios.three_net_scenario", - "two_networks": "netsecgame.coordinator.scenarios.two_nets", # same as scenario1 - "two_networks_small": "netsecgame.coordinator.scenarios.two_nets_small", # same as scenario1_small - "two_networks_tiny": "netsecgame.coordinator.scenarios.two_nets_tiny", # same as scenario1_small + "scenario1" : "netsecgame.game.scenarios.scenario_configuration", + "scenario1_small" : "netsecgame.game.scenarios.smaller_scenario_configuration", + "scenario1_tiny" : "netsecgame.game.scenarios.tiny_scenario_configuration", + "one_network": "netsecgame.game.scenarios.one_net", + "three_net_scenario": "netsecgame.game.scenarios.three_net_scenario", + "two_networks": "netsecgame.game.scenarios.two_nets", # same as scenario1 + "two_networks_small": "netsecgame.game.scenarios.two_nets_small", # same as scenario1_small + "two_networks_tiny": "netsecgame.game.scenarios.two_nets_tiny", # same as scenario1_small } scenario_name = self.config['env']['scenario'] diff --git a/netsecgame/coordinator/coordinator.py b/netsecgame/game/coordinator.py similarity index 99% rename from netsecgame/coordinator/coordinator.py rename to netsecgame/game/coordinator.py index b296716e..51d50f20 100644 --- a/netsecgame/coordinator/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -5,9 +5,9 @@ import signal from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig -from netsecgame.coordinator.global_defender import GlobalDefender +from netsecgame.game.global_defender import GlobalDefender from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl -from netsecgame.coordinator.config_parser import ConfigParser +from netsecgame.game.config_parser import ConfigParser import os from aiohttp import ClientSession from cyst.api.environment.environment import Environment diff --git a/netsecgame/coordinator/global_defender.py b/netsecgame/game/global_defender.py similarity index 100% rename from netsecgame/coordinator/global_defender.py rename to netsecgame/game/global_defender.py diff --git a/netsecgame/coordinator/netsecenv_conf.yaml b/netsecgame/game/netsecenv_conf.yaml similarity index 100% rename from netsecgame/coordinator/netsecenv_conf.yaml rename to netsecgame/game/netsecenv_conf.yaml diff --git a/netsecgame/coordinator/netsecevn_conf_cyst_integration.yaml b/netsecgame/game/netsecevn_conf_cyst_integration.yaml similarity index 100% rename from netsecgame/coordinator/netsecevn_conf_cyst_integration.yaml rename to netsecgame/game/netsecevn_conf_cyst_integration.yaml diff --git a/netsecgame/coordinator/scenarios/__init__.py b/netsecgame/game/scenarios/__init__.py similarity index 100% rename from netsecgame/coordinator/scenarios/__init__.py rename to netsecgame/game/scenarios/__init__.py diff --git a/netsecgame/coordinator/scenarios/one_net.py b/netsecgame/game/scenarios/one_net.py similarity index 100% rename from netsecgame/coordinator/scenarios/one_net.py rename to netsecgame/game/scenarios/one_net.py diff --git a/netsecgame/coordinator/scenarios/scenario_configuration.py b/netsecgame/game/scenarios/scenario_configuration.py similarity index 100% rename from netsecgame/coordinator/scenarios/scenario_configuration.py rename to netsecgame/game/scenarios/scenario_configuration.py diff --git a/netsecgame/coordinator/scenarios/smaller_scenario_configuration.py b/netsecgame/game/scenarios/smaller_scenario_configuration.py similarity index 100% rename from netsecgame/coordinator/scenarios/smaller_scenario_configuration.py rename to netsecgame/game/scenarios/smaller_scenario_configuration.py diff --git a/netsecgame/coordinator/scenarios/test_scenario_configuration.py b/netsecgame/game/scenarios/test_scenario_configuration.py similarity index 100% rename from netsecgame/coordinator/scenarios/test_scenario_configuration.py rename to netsecgame/game/scenarios/test_scenario_configuration.py diff --git a/netsecgame/coordinator/scenarios/three_net_scenario.py b/netsecgame/game/scenarios/three_net_scenario.py similarity index 100% rename from netsecgame/coordinator/scenarios/three_net_scenario.py rename to netsecgame/game/scenarios/three_net_scenario.py diff --git a/netsecgame/coordinator/scenarios/tiny_scenario_configuration.py b/netsecgame/game/scenarios/tiny_scenario_configuration.py similarity index 100% rename from netsecgame/coordinator/scenarios/tiny_scenario_configuration.py rename to netsecgame/game/scenarios/tiny_scenario_configuration.py diff --git a/netsecgame/coordinator/scenarios/two_nets.py b/netsecgame/game/scenarios/two_nets.py similarity index 100% rename from netsecgame/coordinator/scenarios/two_nets.py rename to netsecgame/game/scenarios/two_nets.py diff --git a/netsecgame/coordinator/scenarios/two_nets_small.py b/netsecgame/game/scenarios/two_nets_small.py similarity index 100% rename from netsecgame/coordinator/scenarios/two_nets_small.py rename to netsecgame/game/scenarios/two_nets_small.py diff --git a/netsecgame/coordinator/scenarios/two_nets_tiny.py b/netsecgame/game/scenarios/two_nets_tiny.py similarity index 100% rename from netsecgame/coordinator/scenarios/two_nets_tiny.py rename to netsecgame/game/scenarios/two_nets_tiny.py diff --git a/netsecgame/coordinator/worlds/CYSTCoordinator.py b/netsecgame/game/worlds/CYSTCoordinator.py similarity index 99% rename from netsecgame/coordinator/worlds/CYSTCoordinator.py rename to netsecgame/game/worlds/CYSTCoordinator.py index 67f18370..2c0f388e 100644 --- a/netsecgame/coordinator/worlds/CYSTCoordinator.py +++ b/netsecgame/game/worlds/CYSTCoordinator.py @@ -9,7 +9,7 @@ import argparse from pathlib import Path from netsecgame.game_components import GameState, Action, ActionType, IP, Service ,Network -from netsecgame.coordinator.coordinator import GameCoordinator +from netsecgame.game.coordinator import GameCoordinator from cyst.api.configuration.network.node import NodeConfig from netsecgame.utils.utils import get_logging_level diff --git a/netsecgame/coordinator/worlds/NSEGameCoordinator.py b/netsecgame/game/worlds/NSEGameCoordinator.py similarity index 99% rename from netsecgame/coordinator/worlds/NSEGameCoordinator.py rename to netsecgame/game/worlds/NSEGameCoordinator.py index 7e7b0e71..7c774a8e 100644 --- a/netsecgame/coordinator/worlds/NSEGameCoordinator.py +++ b/netsecgame/game/worlds/NSEGameCoordinator.py @@ -13,7 +13,7 @@ from collections import defaultdict from netsecgame.game_components import GameState, Action, ActionType, IP, Network, Data, Service -from netsecgame.coordinator.coordinator import GameCoordinator +from netsecgame.game.coordinator import GameCoordinator from cyst.api.configuration import NodeConfig, RouterConfig, ConnectionConfig, ExploitConfig, FirewallPolicy from netsecgame.utils.utils import get_logging_level diff --git a/netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py b/netsecgame/game/worlds/NSGRealWorldCoordinator.py similarity index 99% rename from netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py rename to netsecgame/game/worlds/NSGRealWorldCoordinator.py index e592e9d1..035a8c93 100644 --- a/netsecgame/coordinator/worlds/NSGRealWorldCoordinator.py +++ b/netsecgame/game/worlds/NSGRealWorldCoordinator.py @@ -11,7 +11,7 @@ from netsecgame.utils.utils import get_logging_level from netsecgame.game_components import GameState, Action, ActionType, Service,IP -from netsecgame.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.game.worlds.NSEGameCoordinator import NSGCoordinator class NSERealWorldGameCoordinator(NSGCoordinator): diff --git a/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py b/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py similarity index 98% rename from netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py rename to netsecgame/game/worlds/WhiteBoxNSGCoordinator.py index 6e360ad1..7040cdc9 100644 --- a/netsecgame/coordinator/worlds/WhiteBoxNSGCoordinator.py +++ b/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py @@ -7,7 +7,7 @@ from pathlib import Path from netsecgame.utils.utils import get_logging_level from netsecgame.game_components import Action, ActionType -from netsecgame.coordinator.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.game.worlds.NSEGameCoordinator import NSGCoordinator class WhiteBoxNSGCoordinator(NSGCoordinator): diff --git a/netsecgame/coordinator/worlds/__init__.py b/netsecgame/game/worlds/__init__.py similarity index 100% rename from netsecgame/coordinator/worlds/__init__.py rename to netsecgame/game/worlds/__init__.py From 9c92318817cd41541f375f003a40295830ff5bdc Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:31:35 +0100 Subject: [PATCH 042/112] apply renaming change --- tests/coordinator/test_agent_server.py | 2 +- tests/coordinator/test_coordinator_core.py | 2 +- tests/coordinator/test_global_defender.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/coordinator/test_agent_server.py b/tests/coordinator/test_agent_server.py index 643641ca..67f57d0a 100644 --- a/tests/coordinator/test_agent_server.py +++ b/tests/coordinator/test_agent_server.py @@ -3,7 +3,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock from contextlib import suppress -from netsecgame.coordinator.coordinator import AgentServer +from netsecgame.game.coordinator import AgentServer from netsecgame.game_components import Action, ActionType, ProtocolConfig # ----------------------- diff --git a/tests/coordinator/test_coordinator_core.py b/tests/coordinator/test_coordinator_core.py index 0a5dee15..ced6a1c0 100644 --- a/tests/coordinator/test_coordinator_core.py +++ b/tests/coordinator/test_coordinator_core.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from types import SimpleNamespace -from netsecgame.coordinator.coordinator import GameCoordinator +from netsecgame.game.coordinator import GameCoordinator from netsecgame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus # ----------------------- diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index b3b53c37..c3535077 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -1,6 +1,6 @@ import pytest from netsecgame.game_components import ActionType, Action -from netsecgame.coordinator.global_defender import GlobalDefender +from netsecgame.game.global_defender import GlobalDefender from unittest.mock import patch @pytest.fixture From d6522d34f12287d4d73be80dfa7324ae388f8ad0 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:33:46 +0100 Subject: [PATCH 043/112] Change name --- netsecgame/game/worlds/NSGRealWorldCoordinator.py | 4 ++-- .../game/worlds/{NSEGameCoordinator.py => NetSecGame.py} | 2 +- netsecgame/game/worlds/WhiteBoxNSGCoordinator.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename netsecgame/game/worlds/{NSEGameCoordinator.py => NetSecGame.py} (99%) diff --git a/netsecgame/game/worlds/NSGRealWorldCoordinator.py b/netsecgame/game/worlds/NSGRealWorldCoordinator.py index 035a8c93..aafcc5bb 100644 --- a/netsecgame/game/worlds/NSGRealWorldCoordinator.py +++ b/netsecgame/game/worlds/NSGRealWorldCoordinator.py @@ -11,9 +11,9 @@ from netsecgame.utils.utils import get_logging_level from netsecgame.game_components import GameState, Action, ActionType, Service,IP -from netsecgame.game.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.game.worlds.NetSecGame import NetSecGame -class NSERealWorldGameCoordinator(NSGCoordinator): +class RealWorldNetSecGame(NetSecGame): def _execute_action(self, current_state:GameState, action:Action)-> GameState: """ diff --git a/netsecgame/game/worlds/NSEGameCoordinator.py b/netsecgame/game/worlds/NetSecGame.py similarity index 99% rename from netsecgame/game/worlds/NSEGameCoordinator.py rename to netsecgame/game/worlds/NetSecGame.py index 7c774a8e..940f12a5 100644 --- a/netsecgame/game/worlds/NSEGameCoordinator.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -18,7 +18,7 @@ from netsecgame.utils.utils import get_logging_level -class NSGCoordinator(GameCoordinator): +class NetSecGame(GameCoordinator): def __init__(self, game_host, game_port, task_config:str, allowed_roles=["Attacker", "Defender", "Benign"], seed=None): super().__init__(game_host, game_port, service_host=None, service_port=None, allowed_roles=allowed_roles, task_config_file=task_config) diff --git a/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py b/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py index 7040cdc9..c0a2fbbb 100644 --- a/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py +++ b/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py @@ -7,12 +7,12 @@ from pathlib import Path from netsecgame.utils.utils import get_logging_level from netsecgame.game_components import Action, ActionType -from netsecgame.game.worlds.NSEGameCoordinator import NSGCoordinator +from netsecgame.game.worlds.NetSecGame import NetSecGame -class WhiteBoxNSGCoordinator(NSGCoordinator): +class WhiteBoxNetSecGame(NetSecGame): """ - WhiteBoxNSGCoordinator is an extension for the NetSecGame environment + WhiteBoxNetSecGame is an extension for the NetSecGame environment that provides list of all possible actions to each agent that registers in the game. """ def __init__(self, game_host, game_port, task_config, allowed_roles=["Attacker", "Defender", "Benign"], seed=42, include_block_action=False): From d9ffb6e84fddc2f56983abac883957177e25e25d Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:34:35 +0100 Subject: [PATCH 044/112] Change naming --- .../worlds/{NSGRealWorldCoordinator.py => RealWorldNetSecGame.py} | 0 .../worlds/{WhiteBoxNSGCoordinator.py => WhiteBoxNetSecGame.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename netsecgame/game/worlds/{NSGRealWorldCoordinator.py => RealWorldNetSecGame.py} (100%) rename netsecgame/game/worlds/{WhiteBoxNSGCoordinator.py => WhiteBoxNetSecGame.py} (100%) diff --git a/netsecgame/game/worlds/NSGRealWorldCoordinator.py b/netsecgame/game/worlds/RealWorldNetSecGame.py similarity index 100% rename from netsecgame/game/worlds/NSGRealWorldCoordinator.py rename to netsecgame/game/worlds/RealWorldNetSecGame.py diff --git a/netsecgame/game/worlds/WhiteBoxNSGCoordinator.py b/netsecgame/game/worlds/WhiteBoxNetSecGame.py similarity index 100% rename from netsecgame/game/worlds/WhiteBoxNSGCoordinator.py rename to netsecgame/game/worlds/WhiteBoxNetSecGame.py From 05db84b710275b0cadda9b5ca0607c0be66345f8 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:37:21 +0100 Subject: [PATCH 045/112] Add init to game module --- netsecgame/game/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index e69de29b..57c1e4be 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -0,0 +1,9 @@ +from .coordinator import AgentServer, GameCoordinator +from .worlds.NetSecGame import NetSecGame +from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame +__all__ = [ + 'AgentServer', + 'GameCoordinator', + 'NetSecGame', + 'WhiteBoxNetSecGame' +] \ No newline at end of file From d7eda84f419346fa8f6ebb718adf7ea0f0eda350 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:48:57 +0100 Subject: [PATCH 046/112] Add check for optional dependencies --- netsecgame/game/__init__.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index 57c1e4be..b4da9abc 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -1,9 +1,24 @@ -from .coordinator import AgentServer, GameCoordinator -from .worlds.NetSecGame import NetSecGame -from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame -__all__ = [ - 'AgentServer', - 'GameCoordinator', - 'NetSecGame', - 'WhiteBoxNetSecGame' -] \ No newline at end of file +try: + # Attempt to import server-specific dependencies + import cyst + import aiohttp + import faker + import numpy as np + import requests + + from .coordinator import AgentServer, GameCoordinator + from .worlds.NetSecGame import NetSecGame + from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame + + __all__ = [ + 'AgentServer', + 'GameCoordinator', + 'NetSecGame', + 'WhiteBoxNetSecGame' + ] +except ImportError as e: + raise ImportError( + f"Failed to import 'netsecgame.worlds'. This module requires server dependencies.\n" + f"Missing dependency: {e.name}\n" + f"Please install them using: pip install 'netsecgame[server]'" + ) from e \ No newline at end of file From 38b61c39fa26c22f3703dbbf2513fe3ddbae1372 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:51:04 +0100 Subject: [PATCH 047/112] Fix naming and paths --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a77dae6e..388791a1 100755 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ docker run -d --rm --name netsecgame-server ^ #### Locally The environment can be started locally with from the root folder of the repository with following command: ```bash -python3 -m netsecgame.coordinator.worlds.NSEGameCoordinator \ +python3 -m netsecgame.game.NetSecGame \ --task_config=./examples/example_task_configuration.yaml \ --game_port=9000 --debug_level="INFO" @@ -158,27 +158,28 @@ The architecture of the environment can be seen [here](docs/Architecture.md). The NetSecGame environment has several components in the following files: ``` ├── NetSecgame/ -| ├── base_agent.py # Basic agent class. Defines the API for agent-server communication -| ├── game_components.py # contains basic building blocks of the environment -| ├── utils/ -| ├── utils.py -| ├── log_parser.py -| ├── gamaplay_graphs.py -| ├── actions_parser.py -| ├── coordinator/ # contains components required for running the game server +| ├── agents/ +| ├── base_agent.py # Basic agent class. Defines the API for agent-server communication +| ├── game/ | ├── scenarios/ | ├── tiny_scenario_configuration.py | ├── smaller_scenario_configuration.py | ├── scenario_configuration.py | ├── three_net_configuration.py | ├── worlds/ -| ├── NSGCoordinator.py # basic simulation, pure NSG -| ├── NSGRealWorldCoordinator.py # Extension of `NSGCoordinator` - runs actions in the *network of the host computer* -| ├── CYSTCoordinator.py # Extension of `NSGCoordinator` - runs simulation in CYST engine. -| ├── WhiteBoxNSGCoordinator.py # Extension of `NSGCoordinator` - provides agents with full list of actions upon registration. +| ├── NetSecGame.py # (NSG) basic simulation +| ├── RealWorldNetSecGame.py # Extension of `NSG` - runs actions in the *network of the host computer* +| ├── CYSTCoordinator.py # Extension of `NSG` - runs simulation in CYST engine. +| ├── WhiteBoxNetSecGame.py # Extension of `NSG` - provides agents with full list of actions upon registration. | ├── config_parser.py # NSG task configuration parser | ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) | ├── global_defender.py # Stochastic (non-agentic defender) +| ├── game_components.py # contains basic building blocks of the environment +| ├── utils/ +| ├── utils.py +| ├── log_parser.py +| ├── gamaplay_graphs.py +| ├── actions_parser.py ``` #### Directory Details - `coordinator.py`: Basic coordinator class. Handles agent communication and coordination. **Does not implement dynamics of the world** and must be extended (see examples in `worlds/`). From 803b0b52dd5493d813f396e303ff52c0528973b5 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 11:51:31 +0100 Subject: [PATCH 048/112] Fix path --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7f623928..648e3ce9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN if [ -f pyproject.toml ]; then pip install .[server] ; fi EXPOSE 9000 # Run the Python script when the container launches (with default arguments --task_config=netsecenv_conf.yaml --game_port=9000 --game_host=0.0.0.0) -ENTRYPOINT ["python3", "-m", "netsecgame.coordinator.worlds.NSEGameCoordinator", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] +ENTRYPOINT ["python3", "-m", "netsecgame.game.NetSecGame", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] # Default command arguments (can be overridden at runtime) CMD ["--debug_level=INFO"] From 88fbb4b0a7bcdec2df479286e5d0b143062593a8 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 12:02:54 +0100 Subject: [PATCH 049/112] Fix the names --- netsecgame/game/worlds/NetSecGame.py | 2 +- netsecgame/game/worlds/RealWorldNetSecGame.py | 2 +- netsecgame/game/worlds/WhiteBoxNetSecGame.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index 940f12a5..894008ac 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -1173,6 +1173,6 @@ async def reset(self)->bool: level=pass_level, ) - game_server = NSGCoordinator(args.game_host, args.game_port, args.task_config, seed=args.seed) + game_server = NetSecGame(args.game_host, args.game_port, args.task_config, seed=args.seed) # Run it! game_server.run() \ No newline at end of file diff --git a/netsecgame/game/worlds/RealWorldNetSecGame.py b/netsecgame/game/worlds/RealWorldNetSecGame.py index aafcc5bb..08a4fb50 100644 --- a/netsecgame/game/worlds/RealWorldNetSecGame.py +++ b/netsecgame/game/worlds/RealWorldNetSecGame.py @@ -188,6 +188,6 @@ def _execute_find_services_action_real_world(self, current_state:GameState, acti level=pass_level, ) - game_server = NSERealWorldGameCoordinator(args.game_host, args.game_port, args.task_config) + game_server = RealWorldNetSecGame(args.game_host, args.game_port, args.task_config) # Run it! game_server.run() \ No newline at end of file diff --git a/netsecgame/game/worlds/WhiteBoxNetSecGame.py b/netsecgame/game/worlds/WhiteBoxNetSecGame.py index c0a2fbbb..b6135fd6 100644 --- a/netsecgame/game/worlds/WhiteBoxNetSecGame.py +++ b/netsecgame/game/worlds/WhiteBoxNetSecGame.py @@ -200,6 +200,6 @@ def _create_state_from_view(self, view, add_neighboring_nets = True): level=pass_level, ) - game_server = WhiteBoxNSGCoordinator(args.game_host, args.game_port, args.task_config, seed=args.seed) + game_server = WhiteBoxNetSecGame(args.game_host, args.game_port, args.task_config, seed=args.seed) # Run it! game_server.run() \ No newline at end of file From 008a08b1d796b5f9ea7202b5edb063567a4a3998 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 12:04:15 +0100 Subject: [PATCH 050/112] Adjust the path when running from with python -m --- Dockerfile | 2 +- README.md | 2 +- netsecgame/game/__init__.py | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 648e3ce9..58a470ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN if [ -f pyproject.toml ]; then pip install .[server] ; fi EXPOSE 9000 # Run the Python script when the container launches (with default arguments --task_config=netsecenv_conf.yaml --game_port=9000 --game_host=0.0.0.0) -ENTRYPOINT ["python3", "-m", "netsecgame.game.NetSecGame", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] +ENTRYPOINT ["python3", "-m", "netsecgame.game.worlds.NetSecGame", "--task_config=netsecenv_conf.yaml", "--game_port=9000", "--game_host=0.0.0.0"] # Default command arguments (can be overridden at runtime) CMD ["--debug_level=INFO"] diff --git a/README.md b/README.md index 388791a1..abe7efe3 100755 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ docker run -d --rm --name netsecgame-server ^ #### Locally The environment can be started locally with from the root folder of the repository with following command: ```bash -python3 -m netsecgame.game.NetSecGame \ +python3 -m netsecgame.game.worlds.NetSecGame \ --task_config=./examples/example_task_configuration.yaml \ --game_port=9000 --debug_level="INFO" diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index b4da9abc..807b3f88 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -6,16 +6,16 @@ import numpy as np import requests - from .coordinator import AgentServer, GameCoordinator - from .worlds.NetSecGame import NetSecGame - from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame + # from .coordinator import AgentServer, GameCoordinator + # from .worlds.NetSecGame import NetSecGame + # from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame - __all__ = [ - 'AgentServer', - 'GameCoordinator', - 'NetSecGame', - 'WhiteBoxNetSecGame' - ] + # __all__ = [ + # 'AgentServer', + # 'GameCoordinator', + # 'NetSecGame', + # 'WhiteBoxNetSecGame' + # ] except ImportError as e: raise ImportError( f"Failed to import 'netsecgame.worlds'. This module requires server dependencies.\n" From 72dbe07b57417c88305c057e513f508f31c07c06 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:38:42 +0100 Subject: [PATCH 051/112] Fix ImportError handelling --- netsecgame/game/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index 807b3f88..bd3c052e 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -18,7 +18,7 @@ # ] except ImportError as e: raise ImportError( - f"Failed to import 'netsecgame.worlds'. This module requires server dependencies.\n" + f"Failed to import 'netsecgame.game'. This module requires server dependencies.\n" f"Missing dependency: {e.name}\n" f"Please install them using: pip install 'netsecgame[server]'" - ) from e \ No newline at end of file + ) from None \ No newline at end of file From 56314f78d1b1c56e676b885e67f676911282ccc7 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:44:50 +0100 Subject: [PATCH 052/112] fix patch path --- tests/coordinator/test_global_defender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/coordinator/test_global_defender.py b/tests/coordinator/test_global_defender.py index c3535077..32234b0c 100644 --- a/tests/coordinator/test_global_defender.py +++ b/tests/coordinator/test_global_defender.py @@ -57,6 +57,6 @@ def test_mock_stochastic_probabilities(defender, episode_actions): action = Action(ActionType.ScanNetwork, {}) episode_actions += [{"action_type": str(ActionType.ScanNetwork)}] * 4 # Exceed threshold - with patch("NetSecGame.coordinator.global_defender.random", return_value=0.01): # Force detection probability + with patch("netsecgame.game.global_defender.random", return_value=0.01): # Force detection probability result = defender.stochastic_with_threshold(action, episode_actions, tw_size=5) assert result # Should be True since we forced a low probability value \ No newline at end of file From 8f52e0ead8bd606ff928547a1a51395351b425b5 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:46:33 +0100 Subject: [PATCH 053/112] remove double import for Data --- netsecgame/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index 07a1efd9..ab241cd8 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -12,7 +12,6 @@ GameState, GameStatus, IP, - Data, Network, Observation, ProtocolConfig, From 238cacec813e2c783d867d9ebd43ca1d2ff8b718 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:47:28 +0100 Subject: [PATCH 054/112] remove unneccessary init file --- netsecgame/utils/__init__.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 netsecgame/utils/__init__.py diff --git a/netsecgame/utils/__init__.py b/netsecgame/utils/__init__.py deleted file mode 100644 index e8a62729..00000000 --- a/netsecgame/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# all of the utility functions from utils module are accessible from the top-level netsecgame package -from . import utils \ No newline at end of file From 1795b28d8dcaa5c25cd753472dff571140848f4a Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:48:46 +0100 Subject: [PATCH 055/112] Add comment to disable ruff errors on the unused imports --- netsecgame/game/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index bd3c052e..02340f55 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -1,10 +1,11 @@ try: # Attempt to import server-specific dependencies - import cyst - import aiohttp - import faker - import numpy as np - import requests + # disable ruff error F401 for unused imports (used for dependency checking) + import cyst # noqa: F401 + import aiohttp # noqa: F401 + import faker # noqa: F401 + import numpy as np # noqa: F401 + import requests # noqa: F401 # from .coordinator import AgentServer, GameCoordinator # from .worlds.NetSecGame import NetSecGame From 5385d4a727113c00caa877868070fd5b0ace7ff9 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 13:49:42 +0100 Subject: [PATCH 056/112] Better explanation of the logic of the import checks --- netsecgame/game/__init__.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/netsecgame/game/__init__.py b/netsecgame/game/__init__.py index 02340f55..88c7adad 100644 --- a/netsecgame/game/__init__.py +++ b/netsecgame/game/__init__.py @@ -6,18 +6,9 @@ import faker # noqa: F401 import numpy as np # noqa: F401 import requests # noqa: F401 - - # from .coordinator import AgentServer, GameCoordinator - # from .worlds.NetSecGame import NetSecGame - # from .worlds.WhiteBoxNetSecGame import WhiteBoxNetSecGame - - # __all__ = [ - # 'AgentServer', - # 'GameCoordinator', - # 'NetSecGame', - # 'WhiteBoxNetSecGame' - # ] except ImportError as e: + # If any server-specific dependency is missing, raise an informative error + # Surpress the context of the original ImportError raise ImportError( f"Failed to import 'netsecgame.game'. This module requires server dependencies.\n" f"Missing dependency: {e.name}\n" From bb74c04004fe84c55ff21739409edcde7b700011 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:04:28 +0100 Subject: [PATCH 057/112] moved to the documentation --- docs/game_components.md | 95 +++++++++++++++++++++++++++++++++- netsecgame/docs/Components.md | 97 ----------------------------------- 2 files changed, 94 insertions(+), 98 deletions(-) delete mode 100644 netsecgame/docs/Components.md diff --git a/docs/game_components.md b/docs/game_components.md index 109af552..3821a68f 100644 --- a/docs/game_components.md +++ b/docs/game_components.md @@ -1,2 +1,95 @@ # Game Components -::: AIDojoCoordinator.game_components \ No newline at end of file +There are several builing blocks of the NetSecGame environment that represent the enities in the world. They are use both in the [Actions](#actions) and [GameState](#gamestate). + +### IP +IP is immutable object that represents an IPv4 object in the NetSecGame. It has a single parameter of the address in a dot-decimal notation (4 octet represeted as decimal value separeted by dots). + +Example: `ip = IP("192.168.1.1")` + +### Network +Network is immutable object that represents an IPv4 network object in the NetSecGame. It has 2 parameters: +- `network_ip:str` representing the IPv4 address of the network. +- `mask:int` representing the mask in the CIDR notation. + +Example: `net = Network("192.168.1.0", 24)` + +## Service +Service class holds information about services running in hosts. Each Service has four parameters: +- `name`:str - Name of the service (e.g., "SSH") +- `type`:str - `passive` or `active`. Currently not being used. +- `version`:str - version of the service. +- `is_local`:bool - flag specifying if the service is local only. (if `True`, service is NOT visible without controlling the host). + +Example: `s = Service('postgresql', 'passive', '14.3.0', False)` + +## Data +Data class holds information about datapoints (files) present in the NetSecGame. Datapoints DO NOT hold the content of files. +Each data instance has two parameters: +- `owner`:str - specifying the user who owns this datapoint +- `id`: str - unique identifier of the datapoint in a host +- `size`: int - size of the datapoint (optional, default=0) +- `type`: str - identification of a type of the file (optional, default="") +- `content`: str - content of the data. Default = "" + +Examples:`Data("User1", "DatabaseData")`, `Data("User1", "DatabaseData", size=42, type="txt")` + +## GameState +GameState is an object that represents a view of the NetSecGame environment in a given state. It is constructed as a collection of 'assets' known to the agent. GameState has following parts: +- `known_networks`: Set of [Network](#network) objects that the agent is aware of +- `known_hosts`: Set of [IP](#ip) objects that the agent is aware of +- `controlled_hosts`: Set of [IP](#ip) objetcs that the agent has control over. Note that `controlled_hosts` is a subset of `known_hosts`. +- `known_services`: Dictionary of services that the agent is aware of. +The dictionary format: {`IP`: {`Service`}} where [IP](#ip) object is a key and the value is a set of [Service](#service) objects located in the `IP`. +- `known_data`: Dictionary of data instances that the agent is aware of. The dictionary format: {`IP`: {`Data`}} where [IP](#ip) object is a key and the value is a set of [Data](#data) objects located in the `IP`. +- `known_blocks`: Dictionary of firewall blocks the agent is aware of. It is a dictionary with format: {`target_IP`: {`blocked_IP`, `blocked_IP`}}. Where `target_IP` is the [IP](#ip) where the FW rule was applied (usually a router) and `blocked_IP` is the IP address that is blocked. For now the blocks happen in both input and output direction simultaneously. + + +## Actions +Actions are the objects sent by the agents to the environment. Each action is evaluated by AIDojo and executed if +1. Is a valid Action +2. Can be processed in the current state of the environment + +In all cases, when an agent sends an action to AIDojo, it is given a response. +### Action format +The Action class is defined in `game_components.py`. It has two basic parts: +1. ActionType:Enum +2. parameters:dict + +ActionType is unique Enum that determines what kind of action is agent playing. Parameters are passed in a dictionary as follows. +### List of actions +- **JoinGame**, params={`agent_info`:AgentInfo(\, \)}: Used to register agent in a game with a given \. +- **QuitGame**, params={}: Used for termination of agent's interaction. +- **ResetGame**, params={`request_trajectory`:`bool`(default=`False`), `randomize_topology`:`bool` (default=`False`)}: Used for requesting reset of the game to it's initial position. If `request_trajectory = True`, the coordinator will send back the complete trajectory of the previous run in the next message. +If `randomize_topology=True`, the agent requests randomization of IPs for the next episode. NOTE: randomization takes place only if all playing agents request it. +--- +- **ScanNetwork**, params{`source_host`:\, `target_network`:\}: Scans the given \ from a specified source host. Discovers ALL hosts in a network that are accessible from \. If successful, returns set of discovered \ objects. +- **FindServices**, params={`source_host`:\, `target_host`:\}: Used to discover ALL services running in the `target_host` if the host is accessible from `source_host`. If successful, returns a set of all discovered \ objects. +- **FindData**, params={`source_host`:\, `target_host`:\}: Searches `target_host` for data. If `source_host` differs from `target_host`, success depends on accessability from the `source_host`. If successful, returns a set of all discovered \ objects. +- **ExploitService**, params={`source_host`:\, `target_host`:\, `taget_service`:\}: Exploits `target_service` in a specified `target_host`. If successful, the attacker gains control of the `target_host`. +- **ExfiltrateData**, params{`source_host`:\, `target_host`:\, `data`:\}: Copies `data` from the `source_host` to `target_host` IF both are controlled and `target_host` is accessible from `source_host`. + +### Action preconditions and effects +In the following table, we describe the effects of selected actions and their preconditions. Note that if the preconditions are not satisfied, the actions's effects are not applied. + +| Action | Params | Preconditions | Effects | +|----------------------|----------------------|----------------------|----------------------| +| ScanNetwork| `source_host`, `target_network`| `source_host` ∈ `controlled_hosts`| extends `known_networks`| +|FindServices| `source_host`, `target_host`| `source_host` ∈ `controlled_hosts`| extends `known_services` AND `known_hosts`| +|FindData| `source_host`, `target_host`| `source_host`, `target_host` ∈ `controlled_hosts`| extends `known_data`| +|Exploit Service | `source_host`, `target_host`, `target_service`|`source_host` ∈ `controlled_hosts`| extends `controlled_hosts` with `target_host`| +ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host` ∈ `controlled_hosts` AND `data` ∈ `known_data`| extends `known_data[target_host]` with `data`| + +#### Assumption and Conditions for Actions +1. When playing the `ExploitService` action, it is expected that the agent has discovered this service before (by playing `FindServices` in the `target_host` before this action) +2. The `Find Data` action finds all the available data in the host if successful. +3. The `Find Data` action requires ownership of the target host. +4. Playing `ExfiltrateData` requires controlling **BOTH** source and target hosts +5. Playing `Find Services` can be used to discover hosts (if those have any active services) +6. Parameters of `ScanNetwork` and `FindServices` can be chosen arbitrarily (they don't have to be listed in `known_newtworks`/`known_hosts`) + +## Observations +After submitting Action `a` to the environment, agents receive an `Observation` in return. Each observation consists of 4 parts: +- `state`:`Gamestate` - with the current view of the environment [state](#gamestate) +- `reward`: `int` - with the immediate reward agent gets for playing Action `a` +- `end`:`bool` - indicating if the interaction can continue after playing Action `a` +- `info`: `dict` - placeholder for any information given to the agent (e.g., the reason why `end is True` ) diff --git a/netsecgame/docs/Components.md b/netsecgame/docs/Components.md deleted file mode 100644 index bbe13c3d..00000000 --- a/netsecgame/docs/Components.md +++ /dev/null @@ -1,97 +0,0 @@ -# Game Components -Here, you can see the details of all components of the NetSetEnvironment and their usage. These components are located in [game_components.py](game_components.py). - -## Building blocks -The following classes are used in the game to hold information about the state of the game. They are used both in the [Actions](#actions) and [GameState](#gamestate). - -### IP -IP is immutable object that represents an IPv4 object in the NetSecGame. It has a single parameter of the address in a dot-decimal notation (4 octet represeted as decimal value separeted by dots). - -Example: `ip = IP("192.168.1.1")` - -### Network -Network is immutable object that represents an IPv4 network object in the NetSecGame. It has 2 parameters: -- `network_ip:str` representing the IPv4 address of the network. -- `mask:int` representing the mask in the CIDR notation. - -Example: `net = Network("192.168.1.0", 24)` - -## Service -Service class holds information about services running in hosts. Each Service has four parameters: -- `name`:str - Name of the service (e.g., "SSH") -- `type`:str - `passive` or `active`. Currently not being used. -- `version`:str - version of the service. -- `is_local`:bool - flag specifying if the service is local only. (if `True`, service is NOT visible without controlling the host). - -Example: `s = Service('postgresql', 'passive', '14.3.0', False)` - -## Data -Data class holds information about datapoints (files) present in the NetSecGame. Datapoints DO NOT hold the content of files. -Each data instance has two parameters: -- `owner`:str - specifying the user who owns this datapoint -- `id`: str - unique identifier of the datapoint in a host -- `size`: int - size of the datapoint (optional, default=0) -- `type`: str - identification of a type of the file (optional, default="") - -Examples:`Data("User1", "DatabaseData")`, `Data("User1", "DatabaseData", size=42, type="txt")` - -## GameState -GameState is an object that represents a view of the NetSecGame environment in a given state. It is constructed as a collection of 'assets' available to the agent. GameState has following parts: -- `known_networks`: Set of [Network](#network) objects that the agent is aware of -- `known_hosts`: Set of [IP](#ip) objects that the agent is aware of -- `controlled_hosts`: Set of [IP](#ip) objetcs that the agent has control over. Note that `controlled_hosts` is a subset of `known_hosts`. -- `known_services`: Dictionary of services that the agent is aware of. -The dictionary format: {`IP`: {`Service`}} where [IP](#ip) object is a key and the value is a set of [Service](#service) objects located in the `IP`. -- `known_data`: Dictionary of data instances that the agent is aware of. The dictionary format: {`IP`: {`Data`}} where [IP](#ip) object is a key and the value is a set of [Data](#data) objects located in the `IP`. -- `known_blocks`: Dictionary of firewall blocks the agent is aware of. It is a dictionary with format: {`target_IP`: {`blocked_IP`, `blocked_IP`}}. Where `target_IP` is the [IP](#ip) where the FW rule was applied (usually a router) and `blocked_IP` is the IP address that is blocked. For now the blocks happen in both input and output direction simultaneously. - - -## Actions -Actions are the objects sent by the agents to the environment. Each action is evaluated by AIDojo and executed if -1. Is a valid Action -2. Can be processed in the current state of the environment - -In all cases, when an agent sends an action to AIDojo, it is given a response. -### Action format -The Action class is defined in `game_components.py`. It has two basic parts: -1. ActionType:Enum -2. parameters:dict - -ActionType is unique Enum that determines what kind of action is agent playing. Parameters are passed in a dictionary as follows. -### List of actions -- **JoinGame**, params={`agent_info`:AgentInfo(\, \)}: Used to register agent in a game with a given \. -- **QuitGame**, params={}: Used for termination of agent's interaction. -- **ResetGame**, params={`request_trajectory`:`bool`(default=`False`), `randomize_topology`:`bool` (default=`False`)}: Used for requesting reset of the game to it's initial position. If `request_trajectory = True`, the coordinator will send back the complete trajectory of the previous run in the next message. -If `randomize_topology=True`, the agent requests randomization of IPs for the next episode. NOTE: randomization takes place only if all playing agents request it. ---- -- **ScanNetwork**, params{`source_host`:\, `target_network`:\}: Scans the given \ from a specified source host. Discovers ALL hosts in a network that are accessible from \. If successful, returns set of discovered \ objects. -- **FindServices**, params={`source_host`:\, `target_host`:\}: Used to discover ALL services running in the `target_host` if the host is accessible from `source_host`. If successful, returns a set of all discovered \ objects. -- **FindData**, params={`source_host`:\, `target_host`:\}: Searches `target_host` for data. If `source_host` differs from `target_host`, success depends on accessability from the `source_host`. If successful, returns a set of all discovered \ objects. -- **ExploitService**, params={`source_host`:\, `target_host`:\, `taget_service`:\}: Exploits `target_service` in a specified `target_host`. If successful, the attacker gains control of the `target_host`. -- **ExfiltrateData**, params{`source_host`:\, `target_host`:\, `data`:\}: Copies `data` from the `source_host` to `target_host` IF both are controlled and `target_host` is accessible from `source_host`. - -### Action preconditions and effects -In the following table, we describe the effects of selected actions and their preconditions. Note that if the preconditions are not satisfied, the actions's effects are not applied. - -| Action | Params | Preconditions | Effects | -|----------------------|----------------------|----------------------|----------------------| -| ScanNetwork| `source_host`, `target_network`| `source_host` ∈ `controlled_hosts`| extends `known_networks`| -|FindServices| `source_host`, `target_host`| `source_host` ∈ `controlled_hosts`| extends `known_services` AND `known_hosts`| -|FindData| `source_host`, `target_host`| `source_host`, `target_host` ∈ `controlled_hosts`| extends `known_data`| -|Exploit Service | `source_host`, `target_host`, `target_service`|`source_host` ∈ `controlled_hosts`| extends `controlled_hosts` with `target_host`| -ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host` ∈ `controlled_hosts` AND `data` ∈ `known_data`| extends `known_data[target_host]` with `data`| - -#### Assumption and Conditions for Actions -1. When playing the `ExploitService` action, it is expected that the agent has discovered this service before (by playing `FindServices` in the `target_host` before this action) -2. The `Find Data` action finds all the available data in the host if successful. -3. The `Find Data` action requires ownership of the target host. -4. Playing `ExfiltrateData` requires controlling **BOTH** source and target hosts -5. Playing `Find Services` can be used to discover hosts (if those have any active services) -6. Parameters of `ScanNetwork` and `FindServices` can be chosen arbitrarily (they don't have to be listed in `known_newtworks`/`known_hosts`) - -## Observations -After submitting Action `a` to the environment, agents receive an `Observation` in return. Each observation consists of 4 parts: -- `state`:`Gamestate` - with the current view of the environment [state](#gamestate) -- `reward`: `int` - with the immediate reward agent gets for playing Action `a` -- `end`:`bool` - indicating if the interaction can continue after playing Action `a` -- `info`: `dict` - placeholder for any information given to the agent (e.g., the reason why `end is True` ) From c646b2e1300f420c78f8434013836ffbbb765149 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:05:16 +0100 Subject: [PATCH 058/112] remove outdated description --- netsecgame/docs/Trajectory_analysis.md | 31 -------------------------- 1 file changed, 31 deletions(-) delete mode 100644 netsecgame/docs/Trajectory_analysis.md diff --git a/netsecgame/docs/Trajectory_analysis.md b/netsecgame/docs/Trajectory_analysis.md deleted file mode 100644 index e7c330de..00000000 --- a/netsecgame/docs/Trajectory_analysis.md +++ /dev/null @@ -1,31 +0,0 @@ -# Trajectories and Trajectory analusis -Trajectories capture interactions of agents in AI Dojo. They can be stored in a file for future analysis using the configuration option `save_trajectories: True` in `env` section of the task configuration file. Trajectories are stored in a JSON format, one JSON object per line using [jsonlines](https://jsonlines.readthedocs.io/en/latest/). - -### Example of the trajectory -Below we show an example of a trajectory consisting only from 1 step. Starting from state *S1*, the agent takes action*A1* and moves to state *S2* and is awarded with immediate reward `r = -1`: -```json -{ - "agent_name": "ExampleAgent", - "agent_role": "Attacker", - "end_reason": "goal_reached", - "trajectory": - { - "states":[ - "", - "" - ], - "actions":[ - "" - ], - "rewards":[-1] - } -} -``` -`agent_name` and `agent_role` are provided by the agent upon registration in the game. `end_reason` identifies how did the episode end. Currently there are four options: -1. `goal_reached` - the attacker succcessfully reached the goal state and won the game -2. `detected` - the attacker was detected by the defender subsequently lost the game -3. `max_steps` - the agent used the max allowed amount of steps and the episode was terminated -4. `None` - the episode was interrupted before ending and the trajectory is incomplete. - -## Trajectory analysis - From 2db9c092099e7fc327ca981e431457ccd0032c68 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:09:10 +0100 Subject: [PATCH 059/112] documentation cleanup --- docs/configuration.md | 2 + docs/game_components.md | 96 +----------------- docs/game_coordinator.md | 10 ++ netsecgame/docs/Coordinator.md | 49 --------- .../docs/figures/architecture_diagram.jpg | Bin 443694 -> 0 bytes .../figures/message_passing_coordinator.jpg | Bin 517901 -> 0 bytes 6 files changed, 13 insertions(+), 144 deletions(-) delete mode 100644 netsecgame/docs/Coordinator.md delete mode 100644 netsecgame/docs/figures/architecture_diagram.jpg delete mode 100644 netsecgame/docs/figures/message_passing_coordinator.jpg diff --git a/docs/configuration.md b/docs/configuration.md index f4289bf6..b1914a04 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,6 +15,8 @@ The environment part defines the properties of the environment for the task (see - `save_tajectories` - if `True`, interaction of the agents is serialized and stored in a file - `use_dynamic_addresses` - if `True`, the network and IP addresses defined in `scenario` are randomly changed at the beginning of an episode (the network topology is kept as defined in the `scenario`. Relations between networks are kept, IPs inside networks are chosen at random based on the network IP and mask). The change also depend on the input from the agents: + + |Task configuration| Agent reset request | Result| |----------------------|----------------------|----------------------| |`use_dynamic_ips = True` | `randomize_topology = True`| Changed topology | diff --git a/docs/game_components.md b/docs/game_components.md index 3821a68f..c54cdb5a 100644 --- a/docs/game_components.md +++ b/docs/game_components.md @@ -1,95 +1 @@ -# Game Components -There are several builing blocks of the NetSecGame environment that represent the enities in the world. They are use both in the [Actions](#actions) and [GameState](#gamestate). - -### IP -IP is immutable object that represents an IPv4 object in the NetSecGame. It has a single parameter of the address in a dot-decimal notation (4 octet represeted as decimal value separeted by dots). - -Example: `ip = IP("192.168.1.1")` - -### Network -Network is immutable object that represents an IPv4 network object in the NetSecGame. It has 2 parameters: -- `network_ip:str` representing the IPv4 address of the network. -- `mask:int` representing the mask in the CIDR notation. - -Example: `net = Network("192.168.1.0", 24)` - -## Service -Service class holds information about services running in hosts. Each Service has four parameters: -- `name`:str - Name of the service (e.g., "SSH") -- `type`:str - `passive` or `active`. Currently not being used. -- `version`:str - version of the service. -- `is_local`:bool - flag specifying if the service is local only. (if `True`, service is NOT visible without controlling the host). - -Example: `s = Service('postgresql', 'passive', '14.3.0', False)` - -## Data -Data class holds information about datapoints (files) present in the NetSecGame. Datapoints DO NOT hold the content of files. -Each data instance has two parameters: -- `owner`:str - specifying the user who owns this datapoint -- `id`: str - unique identifier of the datapoint in a host -- `size`: int - size of the datapoint (optional, default=0) -- `type`: str - identification of a type of the file (optional, default="") -- `content`: str - content of the data. Default = "" - -Examples:`Data("User1", "DatabaseData")`, `Data("User1", "DatabaseData", size=42, type="txt")` - -## GameState -GameState is an object that represents a view of the NetSecGame environment in a given state. It is constructed as a collection of 'assets' known to the agent. GameState has following parts: -- `known_networks`: Set of [Network](#network) objects that the agent is aware of -- `known_hosts`: Set of [IP](#ip) objects that the agent is aware of -- `controlled_hosts`: Set of [IP](#ip) objetcs that the agent has control over. Note that `controlled_hosts` is a subset of `known_hosts`. -- `known_services`: Dictionary of services that the agent is aware of. -The dictionary format: {`IP`: {`Service`}} where [IP](#ip) object is a key and the value is a set of [Service](#service) objects located in the `IP`. -- `known_data`: Dictionary of data instances that the agent is aware of. The dictionary format: {`IP`: {`Data`}} where [IP](#ip) object is a key and the value is a set of [Data](#data) objects located in the `IP`. -- `known_blocks`: Dictionary of firewall blocks the agent is aware of. It is a dictionary with format: {`target_IP`: {`blocked_IP`, `blocked_IP`}}. Where `target_IP` is the [IP](#ip) where the FW rule was applied (usually a router) and `blocked_IP` is the IP address that is blocked. For now the blocks happen in both input and output direction simultaneously. - - -## Actions -Actions are the objects sent by the agents to the environment. Each action is evaluated by AIDojo and executed if -1. Is a valid Action -2. Can be processed in the current state of the environment - -In all cases, when an agent sends an action to AIDojo, it is given a response. -### Action format -The Action class is defined in `game_components.py`. It has two basic parts: -1. ActionType:Enum -2. parameters:dict - -ActionType is unique Enum that determines what kind of action is agent playing. Parameters are passed in a dictionary as follows. -### List of actions -- **JoinGame**, params={`agent_info`:AgentInfo(\, \)}: Used to register agent in a game with a given \. -- **QuitGame**, params={}: Used for termination of agent's interaction. -- **ResetGame**, params={`request_trajectory`:`bool`(default=`False`), `randomize_topology`:`bool` (default=`False`)}: Used for requesting reset of the game to it's initial position. If `request_trajectory = True`, the coordinator will send back the complete trajectory of the previous run in the next message. -If `randomize_topology=True`, the agent requests randomization of IPs for the next episode. NOTE: randomization takes place only if all playing agents request it. ---- -- **ScanNetwork**, params{`source_host`:\, `target_network`:\}: Scans the given \ from a specified source host. Discovers ALL hosts in a network that are accessible from \. If successful, returns set of discovered \ objects. -- **FindServices**, params={`source_host`:\, `target_host`:\}: Used to discover ALL services running in the `target_host` if the host is accessible from `source_host`. If successful, returns a set of all discovered \ objects. -- **FindData**, params={`source_host`:\, `target_host`:\}: Searches `target_host` for data. If `source_host` differs from `target_host`, success depends on accessability from the `source_host`. If successful, returns a set of all discovered \ objects. -- **ExploitService**, params={`source_host`:\, `target_host`:\, `taget_service`:\}: Exploits `target_service` in a specified `target_host`. If successful, the attacker gains control of the `target_host`. -- **ExfiltrateData**, params{`source_host`:\, `target_host`:\, `data`:\}: Copies `data` from the `source_host` to `target_host` IF both are controlled and `target_host` is accessible from `source_host`. - -### Action preconditions and effects -In the following table, we describe the effects of selected actions and their preconditions. Note that if the preconditions are not satisfied, the actions's effects are not applied. - -| Action | Params | Preconditions | Effects | -|----------------------|----------------------|----------------------|----------------------| -| ScanNetwork| `source_host`, `target_network`| `source_host` ∈ `controlled_hosts`| extends `known_networks`| -|FindServices| `source_host`, `target_host`| `source_host` ∈ `controlled_hosts`| extends `known_services` AND `known_hosts`| -|FindData| `source_host`, `target_host`| `source_host`, `target_host` ∈ `controlled_hosts`| extends `known_data`| -|Exploit Service | `source_host`, `target_host`, `target_service`|`source_host` ∈ `controlled_hosts`| extends `controlled_hosts` with `target_host`| -ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host` ∈ `controlled_hosts` AND `data` ∈ `known_data`| extends `known_data[target_host]` with `data`| - -#### Assumption and Conditions for Actions -1. When playing the `ExploitService` action, it is expected that the agent has discovered this service before (by playing `FindServices` in the `target_host` before this action) -2. The `Find Data` action finds all the available data in the host if successful. -3. The `Find Data` action requires ownership of the target host. -4. Playing `ExfiltrateData` requires controlling **BOTH** source and target hosts -5. Playing `Find Services` can be used to discover hosts (if those have any active services) -6. Parameters of `ScanNetwork` and `FindServices` can be chosen arbitrarily (they don't have to be listed in `known_newtworks`/`known_hosts`) - -## Observations -After submitting Action `a` to the environment, agents receive an `Observation` in return. Each observation consists of 4 parts: -- `state`:`Gamestate` - with the current view of the environment [state](#gamestate) -- `reward`: `int` - with the immediate reward agent gets for playing Action `a` -- `end`:`bool` - indicating if the interaction can continue after playing Action `a` -- `info`: `dict` - placeholder for any information given to the agent (e.g., the reason why `end is True` ) +::: netsecgame.game_components diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md index 40b92a3b..7ac159ee 100644 --- a/docs/game_coordinator.md +++ b/docs/game_coordinator.md @@ -13,5 +13,15 @@ In detail it handles: 7. Registering the GameReset requests and handelling the game resets. To facilitate the communication the coordinator uses a TCP server to which agents connect. The communication is asynchronous and depends of the + +## Connction to other game components +Coordinator, having the role of the middle man in all communication between the agent and the world uses several queues for massing passing and handelling. + +1. `Action queue` is a queue in which the agents submit their actions. It provides N:1 communication channel in which the coordinator receives the inputs. +2. `Answer queues` is a separeate queue **per agent** in which the results of the actions are send to the agent. + +## Episode +The episode starts with sufficient amount of agents registering in the game. Each agent role has a maximum allowed number of steps defined in the task configuration. An episode ends if all agents reach the goal + ::: AIDojoCoordinator.coordinator.AgentServer ::: AIDojoCoordinator.coordinator.GameCoordinator \ No newline at end of file diff --git a/netsecgame/docs/Coordinator.md b/netsecgame/docs/Coordinator.md deleted file mode 100644 index 28a524fb..00000000 --- a/netsecgame/docs/Coordinator.md +++ /dev/null @@ -1,49 +0,0 @@ -# Coordinator -Coordinator is the centerpiece of the game orchestration. It provides an interface between the agents and the AIDojo world. - -1. Registration of new agents in the game -2. Verification of agents' action format -3. Recording (and storing) trajectories of agents -4. Detection of episode ends (either by reaching timout or agents reaching their respective goals) -5. Assigning rewards for each action and at the end of each episode -6. Removing agents from the game -7. Registering the GameReset requests and handelling the game resets. - -## Connction to other game components -Coordinator, having the role of the middle man in all communication between the agent and the world uses several queues for massing passing and handelling. - -1. `Action queue` is a queue in which the agents submit their actions. It provides N:1 communication channel in which the coordinator receives the inputs. -2. `Answer queues` is a separeate queue **per agent** in which the results of the actions are send to the agent. - - -## Main components of the coordinator -`self._actions_queue`: asycnio queue for agents -> coordinator communication -`self._answer_queues`: dictionary of asycnio queues for coordinator -> agent communication (1 queue per agent) -`self._world_action_queue`: asycnio queue for coordinator -> world queue communication -`self._world_response_queue`: asycnio queue for world -> coordinator queue communication -`self.task_config`: Object with the configuration of the scenario -`self.ALLOWED_ROLES`: list of allowed agent roles [`Attacker`, `Defender`, `Benign`] -`self._world`: Instance of `AIDojoWorld`. Implements the dynamics of the world -`self._CONFIG_FILE_HASH`: hash of the configuration file used in the interaction (scenario, topology, etc.). Used for better reproducibility of results -`self._starting_positions_per_role`: dictionary of starting position of each agent type from `self.ALLOWED_ROLES` -`self._win_conditions_per_role`: dictionary of goal state for each agent type from `self.ALLOWED_ROLES` -`self._goal_description_per_role`: dictionary of textual description of goal of each agent type from `self.ALLOWED_ROLES` -`self._steps_limit_per_role`: dictionary of maximum allowed steps per episode for of each agent type from `self.ALLOWED_ROLES` -`self._use_global_defender`: Inditaction of presence of Global defender (deprecated) - -### Agent information components -`self.agents`: information about connected agents {`agent address`: (`agent_name`,`agent_role`)} -`self._agent_steps`: step counter for each agent in the current episode -`self._reset_requests`: dictionary where requests for episode reset are collected (the world resets only if **all** active agents request reset) -`self._randomize_topology_requests`: dictionary where requests for topology randomization are collected (the world randomizes the topology only if **all** active agents request reset) -`self._agent_observations`: current observation per agent -`self._agent_starting_position`: starting position (with wildcards, see [configuration](../README.md#task-configuration)) per agent -`self._agent_states`: current GameState per agent -`self._agent_last_action`: last Action per agent -`self._agent_statuses`: status of each agent. One of AgentStatus -`self._agent_rewards`: dictionary of final reward of each agent in the current episod. Only agent's which can't participate in the ongoing episode are listed. -`self._agent_trajectories`: complete trajectories for each agent in the ongoing episode - - -## Episode -The episode starts with sufficient amount of agents registering in the game. Each agent role has a maximum allowed number of steps defined in the task configuration. An episode ends if all agents reach the goal \ No newline at end of file diff --git a/netsecgame/docs/figures/architecture_diagram.jpg b/netsecgame/docs/figures/architecture_diagram.jpg deleted file mode 100644 index 7c6db506d0edb92ddac240f402131cddeef817f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443694 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⁢m?|#I3Im@>O06t@;9^o}9 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�mc_!*q_CSQg$LL)Kw8p7O8wW>G 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嫼%+A77e$T-SN;>#gOTjAl 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 TzxErHUbc6+?Exv7cLRR|35WWg diff --git a/netsecgame/docs/figures/message_passing_coordinator.jpg b/netsecgame/docs/figures/message_passing_coordinator.jpg deleted file mode 100644 index 703ef42f915cb996f523137a4565da3dd0ba4eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 517901 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%0DCppw1qglZ=aKDtCd$oh}~G{=-jfzib586C8>Vf6 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

k-)u}m}9jM(Xt3JSLUqYa= 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 @oU%OGCQB^g+ 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≈b?3*K`zjp$ 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

|Task configuration| Agent reset request | Result| |----------------------|----------------------|----------------------| diff --git a/docs/figures/scenarios/scenario 1_small.png b/docs/figures/scenarios/scenario_1_small.png similarity index 100% rename from docs/figures/scenarios/scenario 1_small.png rename to docs/figures/scenarios/scenario_1_small.png diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md index 7ac159ee..d600197c 100644 --- a/docs/game_coordinator.md +++ b/docs/game_coordinator.md @@ -23,5 +23,6 @@ Coordinator, having the role of the middle man in all communication between the ## Episode The episode starts with sufficient amount of agents registering in the game. Each agent role has a maximum allowed number of steps defined in the task configuration. An episode ends if all agents reach the goal -::: AIDojoCoordinator.coordinator.AgentServer -::: AIDojoCoordinator.coordinator.GameCoordinator \ No newline at end of file +::: netsecgame.game.coordinator.AgentServer +::: netsecgame.game.coordinator.GameCoordinator +::: netsecgame.game.worlds.NetSecGame.NetSecGame \ No newline at end of file From eac3e9f121e22e9f42d446834dd6cdf2b2a4f063 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:49:56 +0100 Subject: [PATCH 062/112] Align readme and docs/index --- README.md | 8 +-- docs/index.md | 152 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 92 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index abe7efe3..49d4c6cd 100755 --- a/README.md +++ b/README.md @@ -210,9 +210,6 @@ Helper modules: The [scenarios](#definition-of-the-network-topology) define the **topology** of a network (number of hosts, connections, networks, services, data, users, firewall rules, etc.) while the [task-configuration](#task-configuration) is to be used for definition of the exact task for the agent in one of the scenarios (with fix topology). - Agents compatible with the NetSecGame are located in a separate repository [NetSecGameAgents](https://github.com/stratosphereips/NetSecGameAgents/tree/main) - - - ### Assumptions of the NetSecGame 1. NetSecGame works with the closed-world assumption. Only the defined entities exist in the simulation. 2. If the attacker does a successful action in the same step that the defender successfully detects the action, the priority goes to the defender. The reward is a penalty, and the game ends. @@ -498,9 +495,8 @@ For the data exfiltration, we support 3 variants. The full scenario contains 5 c ### Trajectory storing and analysis -The trajectory is a sequence of GameStates, Actions, and rewards in one run of a game. It contains the complete information of the actions played by the agent, the rewards observed and their effect on the state of the environment. Trajectory visualization and analysis tools are described in [Trajectory analysis tools](./docs/Trajectory_analysis.md) - -Trajectories performed by the agents can be stored in a file using the following configuration: +The trajectory is a sequence of GameStates, Actions, and rewards in one run of a game. It contains the complete information of the actions played by the agent, the rewards observed and their effect on the state of the environment. +Trajectories performed by the agents can be stored in a file using the following configuration (on the server side): ```YAML env: save_trajectories: True diff --git a/docs/index.md b/docs/index.md index cf64a337..1579c687 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,55 +1,60 @@ -# Network Security Game - -The NetSecGame (Network Security Game) is a framework for training and evaluation of AI agents in the network security tasks (both offensive and defensive). It is build with [CYST](https://pypi.org/project/cyst/) network simulator and enables rapid development and testing of AI agents in highly configurable scenarios. Examples of implemented agents can be seen in the submodule [NetSecGameAgents](https://github.com/stratosphereips/NetSecGameAgents/tree/main). +# NetSecGame +The NetSecGame (Network Security Game) is a framework for training and evaluation of AI agents in network security tasks (both offensive and defensive). It is built with [CYST](https://pypi.org/project/cyst/) network simulator and enables rapid development and testing of AI agents in highly configurable scenarios. Examples of implemented agents can be seen in the submodule [NetSecGameAgents](https://github.com/stratosphereips/NetSecGameAgents/tree/main). ## Installation Guide -It is recommended to install the NetSecGame in a virual environement: -### Python venv -1. +It is recommended to run the environment in the Docker container. The up-to-date image can be found in [Dockerhub](https://hub.docker.com/r/stratosphereips/netsecgame). +```bash +docker pull stratosphereips/netsecgame +``` +#### Building the image locally +Optionally, you can build the image locally with: +```bash +docker build -t netsecgame:local . +``` + +### Installing from source +In case you need to modify the envirment and run directly, we recommed to insall it in a virtual environemnt (Python vevn or Conda): +#### Python venv +1. Create new virtual environment ```bash -python -m venv +python -m venv ``` -2. +2. Activate newly created venv ```bash source /bin/activate ``` -### Conda -1. +#### Conda +1. Create new conda environment ```bash -conda create --name aidojo python==3.12.10 +conda create --name aidojo python==3.12 ``` -2. +2. Activate newly created conda env ```bash conda activate aidojo ``` -After the virtual environment is activated, install using pip: + +### After preparing virutual environment, install using pip: ```bash pip install -e . ``` -### With Docker -The NetSecGame can be run in a Docker container. You can build the image locally with: -```bash -docker build -t aidojo-nsg-coordinator:latest . -``` -or use the available image from [Dockerhub](https://hub.docker.com/r/stratosphereips/netsecgame). -```bash -docker pull stratosphereips/netsecgame -``` + ## Quick Start -A task configuration needs to be specified to start the NetSecGame (see [Configuration](configuration.md)). For the first step, the example task configuration is recommended: +A task configuration YAML file is required for starting the NetSecGame environment. For the first step, the example task configuration is recommended: + +### Example Configuration ```yaml # Example of the task configuration for NetSecGame # The objective of the Attacker in this task is to locate specific data # and exfiltrate it to a remote C&C server. -# The scenario starts AFTER initial breach of the local network +# The scenario starts AFTER the initial breach of the local network # (the attacker controls 1 local device + the remote C&C server). -coordinator: - agents: +coordinator: + agents: Attacker: # Configuration of 'Attacker' agents - max_steps: 25 - goal: + max_steps: 25 # timout set for the role `Attacker` + goal: # Definition of the goal state description: "Exfiltrate data from Samba server to remote C&C server." is_any_part_of_goal_random: True known_networks: [] @@ -58,10 +63,10 @@ coordinator: known_services: {} known_data: {213.47.23.195: [[User1,DataFromServer1]]} # winning condition known_blocks: {} - start_position: # Defined starting position of the attacker + start_position: # Definition of the starting state (keywords "random" and "all" can be used) known_networks: [] known_hosts: [] - controlled_hosts: [213.47.23.195, random] # + controlled_hosts: [213.47.23.195, random] # keyword 'random' will be replaced by randomly selected IP during initilization known_services: {} known_data: {} known_blocks: {} @@ -86,57 +91,80 @@ coordinator: blocked_ips: {} known_blocks: {} -env: +env: # Environment configuraion scenario: 'two_networks_tiny' # use the smallest topology for this example use_global_defender: False # Do not use global SIEM Defender use_dynamic_addresses: False # Do not randomize IP addresses use_firewall: True # Use firewall save_trajectories: False # Do not store trajectories - required_players: 1 + required_players: 1 # Minimal amount of agents requiered to start the game rewards: # Configurable reward function success: 100 step: -1 fail: -10 false_positive: -5 ``` - -The game can be started with: -```bash -python3 -m AIDojoCoordinator.worlds.NSEGameCoordinator \ - --task_config=./examples/example_config.yaml \ - --game_port=9000 -``` -Upon which the game server is created on `localhost:9000` to which the agents can connect to interact in the NetSecGame. -### Docker Container -When running in the Docker container, the NetSecGame can be started with: +### Starting the NetSecGame +With the configuration ready the environment can be started in selected port +#### In Docker container ```bash -docker run -it --rm \ - -v $(pwd)/examples/example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml \ - -v $(pwd)/logs:/aidojo/logs \ +docker run -d --rm --name nsg-server\ + -v $(pwd)/examples/example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml \ + -v $(pwd)/logs:/netsecgame/logs \ -p 9000:9000 stratosphereips/netsecgame + --debug_level="INFO" ``` -optionally, you can set the logging level with `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): +`--name nsg-server`: specifies the name of the container +`-v :/netsecgame/netsecenv_conf.yaml` : Mapping of the configuration file + +`-v $(pwd)/logs:/netsecgame/logs`: Mapping of the folder where logs are stored + +` -p :9000`: Mapping of the port in which the server runs + +`--debug_level` is an optional parameter to control the logging level `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): +##### Running on Windows (with Docker desktop) +When running on Windows, Docker desktop is required. T +```cmd +docker run -d --rm --name netsecgame-server ^ + -p 9000:9000 ^ + -v "%cd%\examples\example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml" ^ + -v "%cd%\logs:/netsecgame/logs" ^ + stratosphereips/netsecgame:latest + --debug_level="INFO" +``` + +#### Locally +The environment can be started locally with from the root folder of the repository with following command: ```bash -docker run -it --rm \ - -v $(pwd)/examples/example_task_configuration.yaml:/aidojo/netsecenv_conf.yaml \ - -v $(pwd)/logs:/aidojo/logs \ - -p 9000:9000 stratosphereips/netsecgame \ - --debug_level="WARNING" +python3 -m netsecgame.game.worlds.NetSecGame \ + --task_config=./examples/example_task_configuration.yaml \ + --game_port=9000 + --debug_level="INFO" ``` +Upon which the game server is created on `localhost:9000` to which the agents can connect to interact in the NetSecGame. -## Documentation -The NetSecGame environment has several components in the following files: +### Components of the NetSecGame Environment +The NetSecGame has several components in the following files: ``` -├── AIDojoGameCoordinator/ -| ├── game_coordinator.py -| ├── game_components.py -| ├── global_defender.py -| ├── worlds/ -| ├── NSGCoordinator.py -| ├── NSGRealWorldCoordinator.py -| ├── CYSTCoordinator.py -| ├── scenarios/ +├── NetSecgame/ +| ├── agents/ +| ├── base_agent.py # Basic agent class. Defines the API for agent-server communication +| ├── game/ +| ├── scenarios/ +| ├── tiny_scenario_configuration.py +| ├── smaller_scenario_configuration.py +| ├── scenario_configuration.py +| ├── three_net_configuration.py +| ├── worlds/ +| ├── NetSecGame.py # (NSG) basic simulation +| ├── RealWorldNetSecGame.py # Extension of `NSG` - runs actions in the *network of the host computer* +| ├── CYSTCoordinator.py # Extension of `NSG` - runs simulation in CYST engine. +| ├── WhiteBoxNetSecGame.py # Extension of `NSG` - provides agents with full list of actions upon registration. +| ├── config_parser.py # NSG task configuration parser +| ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) +| ├── global_defender.py # Stochastic (non-agentic defender) +| ├── game_components.py # contains basic building blocks of the environment | ├── utils/ | ├── utils.py | ├── log_parser.py From e9da4d18e48a7909e63cae80d3ee74867b401a29 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:59:39 +0100 Subject: [PATCH 063/112] Add warning correctly --- docs/configuration.md | 16 +++++++++++++++- mkdocs.yml | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index e9d64f7e..02d93625 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -202,4 +202,18 @@ Example of defender configuration: blocked_ips: {} known_blocks: {} ``` -As in other agents, the description is only a text for the agent, so it can know what is supposed to do to win. In the curent implementation, the *Defender* wins, if **NO ATTACKER** reaches their goal. \ No newline at end of file +As in other agents, the description is only a text for the agent, so it can know what is supposed to do to win. In the curent implementation, the *Defender* wins, if **NO ATTACKER** reaches their goal. + +### Trajectory storing and analysis + +The trajectory is a sequence of GameStates, Actions, and rewards in one run of a game. It contains the complete information of the actions played by the agent, the rewards observed and their effect on the state of the environment. + +Trajectories performed by the agents can be stored in a file using the following configuration (on the server side): + +```yaml +env: + save_trajectories: True +``` +!!! warning "Caution" + Trajectory files can grow very fast. It is recommended to use this feature on evaluation/testing runs only. By default, this feature is not enabled. + diff --git a/mkdocs.yml b/mkdocs.yml index e5fdccab..ec92336d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ plugins: markdown_extensions: - pymdownx.arithmatex - pymdownx.superfences + - admonition extra_javascript: - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js \ No newline at end of file From 7b64ae8c37f50a41317c31b2f3ca6ce92a61a2c0 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 18:00:09 +0100 Subject: [PATCH 064/112] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49d4c6cd..2556bd22 100755 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ docker run -d --rm --name nsg-server\ `--debug_level` is an optional parameter to control the logging level `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): ##### Running on Windows (with Docker desktop) -When running on Windows, Docker desktop is required. T +When running on Windows, Docker desktop is required. ```cmd docker run -d --rm --name netsecgame-server ^ -p 9000:9000 ^ From 22d10234cd8a6a1ac1e2034dd78013fba5ee6287 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Thu, 22 Jan 2026 11:01:47 +0100 Subject: [PATCH 065/112] Add PyPi classifiers --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d0a8c9cf..7cef0642 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "netsecgame" +name = "netsecgame-test" dynamic = ["version"] description = "A package for coordinating AI-driven network simulation games." readme = "README.md" @@ -19,6 +19,10 @@ dependencies = [ "jsonlines>=4.0.0", "netaddr==0.9.0", ] +classifiers = [ + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] requires-python = ">=3.12" [project.optional-dependencies] From 4bfa5eef70cc1926df53f18417cb16e20c4f5d1c Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Thu, 22 Jan 2026 11:02:01 +0100 Subject: [PATCH 066/112] Fix snippet syntax --- docs/index.md | 4 ++-- mkdocs.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 1579c687..54d590d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -124,8 +124,8 @@ docker run -d --rm --name nsg-server\ `--debug_level` is an optional parameter to control the logging level `--debug_level=["DEBUG", "INFO", "WARNING", "CRITICAL"]` (defaul=`"INFO"`): ##### Running on Windows (with Docker desktop) -When running on Windows, Docker desktop is required. T -```cmd +When running on Windows, Docker desktop is required. +```batch docker run -d --rm --name netsecgame-server ^ -p 9000:9000 ^ -v "%cd%\examples\example_task_configuration.yaml:/netsecgame/netsecenv_conf.yaml" ^ diff --git a/mkdocs.yml b/mkdocs.yml index ec92336d..cd9b945e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ plugins: markdown_extensions: - pymdownx.arithmatex - pymdownx.superfences + - pymdownx.highlight - admonition extra_javascript: From c486c0b026849fc9ead2858e1df925750b84f8b2 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 14:42:30 +0100 Subject: [PATCH 067/112] Add slot attribute to dataclasses to optimize memory usage --- netsecgame/game_components.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index 5ec6a505..a2f995ea 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -12,7 +12,7 @@ import ast -@dataclass(frozen=True, eq=True, order=True) +@dataclass(frozen=True, eq=True, order=True, slots=True) class Service(): """ Represents a service in the NetSecGame. @@ -42,7 +42,7 @@ def from_dict(cls, data: dict)->"Service": return cls(**data) -@dataclass(frozen=True, eq=True, order=True) +@dataclass(frozen=True, eq=True, order=True, slots=True) class IP(): """ Immutable object representing an IPv4 address in the NetSecGame. @@ -125,7 +125,7 @@ def __hash__(self)->int: """ return hash(self.ip) -@dataclass(frozen=True, eq=True) +@dataclass(frozen=True, eq=True, slots=True) class Network(): """ Immutable object representing an IPv4 network in the NetSecGame. @@ -226,7 +226,7 @@ def from_dict(cls, data: dict)->"Network": """ return cls(**data) -@dataclass(frozen=True, eq=True, order=True) +@dataclass(frozen=True, eq=True, order=True, slots=True) class Data(): """ Represents a data object in the NetSecGame. @@ -373,7 +373,7 @@ def from_dict(cls, data: dict)->"AgentInfo": """ return cls(**data) -@dataclass(frozen=True) +@dataclass(frozen=True, slots=True) class Action: """ Immutable dataclass representing an Action. @@ -395,7 +395,8 @@ def as_dict(self) -> Dict[str, Any]: """ params = {} for k, v in self.parameters.items(): - if hasattr(v, '__dict__'): # Handle custom objects like Service, Data, AgentInfo + # Check if v is a dataclass AND ensuring it is an instance, not the class itself + if dataclasses.is_dataclass(v) and not isinstance(v, type): params[k] = asdict(v) elif isinstance(v, bool): # Handle boolean values params[k] = v @@ -403,6 +404,7 @@ def as_dict(self) -> Dict[str, Any]: params[k] = str(v) return {"action_type": str(self.action_type), "parameters": params} + @property def type(self)->ActionType: """ @@ -519,7 +521,7 @@ def __hash__(self) -> int: sorted_params = tuple(sorted((k, hash(v)) for k, v in self.parameters.items())) return hash((self.action_type, sorted_params)) -@dataclass(frozen=True) +@dataclass(frozen=True, slots=True) class GameState(): """ Represents the state of the game. From ee29303e68f0f3928948cce79bbf897efd769fbc Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 14:45:25 +0100 Subject: [PATCH 068/112] Use top-level package error --- netsecgame/game_components.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index a2f995ea..867e788c 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -167,7 +167,7 @@ def __lt__(self, other)->bool: """ try: return netaddr.IPNetwork(str(self)) < netaddr.IPNetwork(str(other)) - except netaddr.core.AddrFormatError: + except netaddr.AddrFormatError: return str(self.ip) < str(other.ip) def __le__(self, other)->bool: @@ -182,7 +182,7 @@ def __le__(self, other)->bool: """ try: return netaddr.IPNetwork(str(self)) <= netaddr.IPNetwork(str(other)) - except netaddr.core.AddrFormatError: + except netaddr.AddrFormatError: return str(self.ip) <= str(other.ip) def __gt__(self, other)->bool: @@ -197,7 +197,7 @@ def __gt__(self, other)->bool: """ try: return netaddr.IPNetwork(str(self)) > netaddr.IPNetwork(str(other)) - except netaddr.core.AddrFormatError: + except netaddr.AddrFormatError: return str(self.ip) > str(other.ip) def is_private(self)->bool: @@ -575,7 +575,7 @@ def as_graph(self)->tuple: if str(host) in netaddr.IPNetwork(str(net)): edges.append((graph_nodes[net], graph_nodes[host])) edges.append((graph_nodes[host], graph_nodes[net])) - except netaddr.core.AddrFormatError as error: + except netaddr.AddrFormatError as error: print(host, self.known_networks, self.known_hosts) print("Error:") print(error) From 4528a5a1c3bae7ef9a92e2ca1f023dd4f8e4aef0 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 14:50:13 +0100 Subject: [PATCH 069/112] Fix return values of the abstracct classes --- netsecgame/game/coordinator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 51d50f20..28b39196 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -863,13 +863,13 @@ async def step(self, agent_id:tuple, agent_state:GameState, action:Action): raise NotImplementedError async def reset(self)->bool: - return NotImplemented + raise NotImplementedError def _initialize(self): """ Initialize the game state and other necessary components. This is called at the start of the game after the configuration is loaded. """ - return NotImplemented + raise NotImplementedError def goal_check(self, agent_addr:tuple)->bool: """ From 229e45b39fcbc69968b83af5059082498a87c308 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 14:50:22 +0100 Subject: [PATCH 070/112] Fix typing errors --- netsecgame/game/worlds/NetSecGame.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index 894008ac..7c70361f 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -665,7 +665,7 @@ def _get_services_from_host(self, host_ip:str, controlled_hosts:set)-> set: """ Returns set of Service tuples from given hostIP """ - found_services = {} + found_services = set() if host_ip in self._ip_to_hostname: #is it existing IP? if self._ip_to_hostname[host_ip] in self._services: #does it have any services? if host_ip in controlled_hosts: # Should local services be included ? @@ -712,7 +712,7 @@ def _get_known_blocks_in_host(self, host_ip:str, controlled_hosts:set)->set: self.logger.debug("\t\t\tCan't get data in host. The host is not controlled.") return known_blocks - def _get_data_content(self, host_ip:str, data_id:str)->str: + def _get_data_content(self, host_ip:str, data_id:str)->str|None: """ Returns content of data identified by a host_ip and data_ip. """ @@ -756,7 +756,7 @@ def _execute_action(self, current_state:GameState, action:Action, agent_id:tuple raise ValueError(f"Unknown Action type or other error: '{action.type}'") return next_state - def _record_false_positive(self, src_ip:IP, dst_ip:IP, agent_id:tuple)->bool: + def _record_false_positive(self, src_ip:IP, dst_ip:IP, agent_id:tuple)-> None: # only record false positive if the agent is benign if self.is_agent_benign(agent_id): # find agent(s) that created the rule From f3327dabbf6b9811190075e551d3ac39e4c5bee6 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 14:54:37 +0100 Subject: [PATCH 071/112] Fix typing --- netsecgame/game/worlds/NetSecGame.py | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index 7c70361f..eda1a3a7 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -9,7 +9,7 @@ import json from faker import Faker from pathlib import Path -from typing import Iterable +from typing import Iterable, Any from collections import defaultdict from netsecgame.game_components import GameState, Action, ActionType, IP, Network, Data, Service @@ -57,17 +57,20 @@ def _initialize(self): None """ # Load CYST configuration - self._process_cyst_config(self._cyst_objects) - # Check if dynamic network and ip adddresses are required - if self._use_dynamic_ips: - self.logger.info("Dynamic change of the IP and network addresses enabled") - self._faker_object = Faker() - Faker.seed(self._seed) - # store initial values for parts which are modified during the game - self._data_original = copy.deepcopy(self._data) - self._data_content_original = copy.deepcopy(self._data_content) - self._firewall_original = copy.deepcopy(self._firewall) - self.logger.info("Environment initialization finished") + if self._cyst_objects is not None: + self._process_cyst_config(self._cyst_objects) + # Check if dynamic network and ip adddresses are required + if self._use_dynamic_ips: + self.logger.info("Dynamic change of the IP and network addresses enabled") + self._faker_object = Faker() + Faker.seed(self._seed) + # store initial values for parts which are modified during the game + self._data_original = copy.deepcopy(self._data) + self._data_content_original = copy.deepcopy(self._data_content) + self._firewall_original = copy.deepcopy(self._firewall) + self.logger.info("Environment initialization finished") + else: + self.logger.error("CYST configuration not loaded, cannot initialize the environment!") def _get_hosts_from_view(self, view_hosts:Iterable, allowed_hosts=None)->set[IP]: """ @@ -295,7 +298,7 @@ def _create_state_from_view(self, view:dict, add_neighboring_nets:bool=True)->Ga self.logger.info(f"Generated GameState:{game_state}") return game_state - def _process_cyst_config(self, configuration_objects:list)-> None: + def _process_cyst_config(self, configuration_objects:list[Any])-> None: """ Process the cyst configuration file """ From 8c200e56946c9ae6665240f4d3f8986592dd9f3e Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 17:15:33 +0100 Subject: [PATCH 072/112] Improved typng and docstrings --- netsecgame/game_components.py | 161 ++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 76 deletions(-) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index 867e788c..d80d00ba 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -1,7 +1,8 @@ +from __future__ import annotations # Author Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz # Library of helpful functions and objects to play the net sec game from dataclasses import dataclass, field, asdict -from typing import Dict, Any +from typing import Dict, Any, List, Set, Tuple, Optional, NamedTuple import dataclasses from collections import namedtuple import json @@ -11,7 +12,6 @@ import ipaddress import ast - @dataclass(frozen=True, eq=True, order=True, slots=True) class Service(): """ @@ -19,9 +19,9 @@ class Service(): Attributes: name (str): Name of the service. - type (str): Type of the service. Default `uknown` - version (str): Version of the service. Default `uknown` - is_local (bool): Whether the service is local. Default True + type (str): Type of the service. Default `unknown`. + version (str): Version of the service. Default `unknown`. + is_local (bool): Whether the service is local. Default True. """ name: str type: str = "unknown" @@ -29,12 +29,12 @@ class Service(): is_local: bool = True @classmethod - def from_dict(cls, data: dict)->"Service": + def from_dict(cls, data: Dict[str, Any]) -> Service: """ Create a Service object from a dictionary. Args: - data (dict): Dictionary with service attributes. + data (Dict[str, Any]): Dictionary with service attributes. Returns: Service: The created Service object. @@ -64,7 +64,7 @@ def __post_init__(self): except ValueError: raise ValueError(f"Invalid IP address provided: {self.ip}") - def __repr__(self)->str: + def __repr__(self) -> str: """ Return the string representation of the IP. @@ -73,26 +73,26 @@ def __repr__(self)->str: """ return self.ip - def __eq__(self, other)->bool: + def __eq__(self, other: object) -> bool: """ Check equality with another IP object. Args: - other (IP): Another IP object. + other (object): Another object to compare with. Returns: - is_equal: True if equal, False otherwise. + bool: True if equal, False otherwise. """ if not isinstance(other, IP): return NotImplemented return self.ip == other.ip - def is_private(self)->bool: + def is_private(self) -> bool: """ Check if the IP address is private. Uses ipaddress module. Returns: - is_private: True if the IP is private, False otherwise. + bool: True if the IP is private, False otherwise. """ try: return ipaddress.IPv4Network(self.ip).is_private @@ -104,12 +104,12 @@ def is_private(self)->bool: return False @classmethod - def from_dict(cls, data: dict)->"IP": + def from_dict(cls, data: Dict[str, Any]) -> IP: """ Build the IP object from a dictionary representation. Args: - data (dict): Dictionary with IP attributes. + data (Dict[str, Any]): Dictionary with IP attributes. Returns: IP: The created IP object. @@ -137,7 +137,7 @@ class Network(): ip: str mask: int - def __repr__(self)->str: + def __repr__(self) -> str: """ Return the string representation of the network. @@ -146,7 +146,7 @@ def __repr__(self)->str: """ return f"{self.ip}/{self.mask}" - def __str__(self)->str: + def __str__(self) -> str: """ Return the string representation of the network. @@ -155,7 +155,7 @@ def __str__(self)->str: """ return f"{self.ip}/{self.mask}" - def __lt__(self, other)->bool: + def __lt__(self, other: Network) -> bool: """ Less-than comparison for networks. @@ -170,7 +170,7 @@ def __lt__(self, other)->bool: except netaddr.AddrFormatError: return str(self.ip) < str(other.ip) - def __le__(self, other)->bool: + def __le__(self, other: Network) -> bool: """ Less-than-or-equal comparison for networks. @@ -185,7 +185,7 @@ def __le__(self, other)->bool: except netaddr.AddrFormatError: return str(self.ip) <= str(other.ip) - def __gt__(self, other)->bool: + def __gt__(self, other: Network) -> bool: """ Greater-than comparison for networks. @@ -200,7 +200,7 @@ def __gt__(self, other)->bool: except netaddr.AddrFormatError: return str(self.ip) > str(other.ip) - def is_private(self)->bool: + def is_private(self) -> bool: """ Check if the network is private. Uses ipaddress module. @@ -214,18 +214,19 @@ def is_private(self)->bool: return True @classmethod - def from_dict(cls, data: dict)->"Network": + def from_dict(cls, data: Dict[str, Any]) -> Network: """ Build the Network object from a dictionary. Args: - data (dict): Dictionary with network attributes. + data (Dict[str, Any]): Dictionary with network attributes. Returns: Network: The created Network object. """ return cls(**data) + @dataclass(frozen=True, eq=True, order=True, slots=True) class Data(): """ @@ -252,13 +253,14 @@ def __hash__(self) -> int: int: The hash value. """ return hash((self.owner, self.id, self.type)) + @classmethod - def from_dict(cls, data: dict)->"Data": + def from_dict(cls, data: Dict[str, Any]) -> Data: """ Build the Data object from a dictionary. Args: - data (dict): Dictionary with data attributes. + data (Dict[str, Any]): Dictionary with data attributes. Returns: Data: The created Data object. @@ -280,7 +282,7 @@ class ActionType(enum.Enum): QuitGame = "QuitGame" ResetGame = "ResetGame" - def to_string(self)->str: + def to_string(self) -> str: """ Convert the ActionType enum to string. @@ -289,12 +291,12 @@ def to_string(self)->str: """ return self.value - def __eq__(self, other)->bool: + def __eq__(self, other: object) -> bool: """ Compare ActionType with another ActionType or string. Args: - other (ActionType or str): The object to compare. + other (object): The object to compare. Returns: bool: True if equal, False otherwise. @@ -307,7 +309,7 @@ def __eq__(self, other)->bool: return self.value == other.replace("ActionType.", "") return False - def __hash__(self)->int: + def __hash__(self) -> int: """ Compute the hash of the ActionType. @@ -318,7 +320,7 @@ def __hash__(self)->int: return hash(self.value) @classmethod - def from_string(cls, name)->"ActionType": + def from_string(cls, name: str) -> ActionType: """ Convert a string to an ActionType enum. Strips 'ActionType.' if present. @@ -350,7 +352,7 @@ class AgentInfo(): name: str role: str - def __repr__(self)->str: + def __repr__(self) -> str: """ Return the string representation of the AgentInfo. @@ -361,12 +363,12 @@ def __repr__(self)->str: @classmethod - def from_dict(cls, data: dict)->"AgentInfo": + def from_dict(cls, data: Dict[str, Any]) -> AgentInfo: """ Build the AgentInfo object from a dictionary. Args: - data (dict): Dictionary with agent info attributes. + data (Dict[str, Any]): Dictionary with agent info attributes. Returns: AgentInfo: The created AgentInfo object. @@ -406,7 +408,7 @@ def as_dict(self) -> Dict[str, Any]: @property - def type(self)->ActionType: + def type(self) -> ActionType: """ Return the action type. @@ -425,7 +427,7 @@ def to_json(self) -> str: return json.dumps(self.as_dict) @classmethod - def from_dict(cls, data_dict: Dict[str, Any]) -> "Action": + def from_dict(cls, data_dict: Dict[str, Any]) -> Action: """ Create an Action from a dictionary. @@ -439,7 +441,7 @@ def from_dict(cls, data_dict: Dict[str, Any]) -> "Action": ValueError: If an unsupported parameter is encountered. """ action_type = ActionType.from_string(data_dict["action_type"]) - params = {} + params: Dict[str, Any] = {} for k, v in data_dict["parameters"].items(): match k: case "source_host" | "target_host" | "blocked_host": @@ -462,7 +464,7 @@ def from_dict(cls, data_dict: Dict[str, Any]) -> "Action": return cls(action_type=action_type, parameters=params) @classmethod - def from_json(cls, json_string: str) -> "Action": + def from_json(cls, json_string: str) -> Action: """ Create an Action from a JSON string. @@ -527,27 +529,28 @@ class GameState(): Represents the state of the game. Attributes: - controlled_hosts (set): Controlled hosts. - known_hosts (set): Known hosts. - known_services (dict): Known services. - known_data (dict): Known data. - known_networks (set): Known networks. - known_blocks (dict): Known blocks. + controlled_hosts (Set[IP]): Controlled hosts. + known_hosts (Set[IP]): Known hosts. + known_services (Dict[IP, Set[Service]]): Known services. + known_data (Dict[IP, Set[Data]]): Known data. + known_networks (Set[Network]): Known networks. + known_blocks (Dict[IP, Set[IP]]): Known blocks. """ - controlled_hosts: set = field(default_factory=set, hash=True) - known_hosts: set = field(default_factory=set, hash=True) - known_services: dict = field(default_factory=dict, hash=True) - known_data: dict = field(default_factory=dict, hash=True) - known_networks: set = field(default_factory=set, hash=True) - known_blocks: dict = field(default_factory=dict, hash=True) + controlled_hosts: Set[IP] = field(default_factory=set, hash=True) + known_hosts: Set[IP] = field(default_factory=set, hash=True) + known_services: Dict[IP, Set[Service]] = field(default_factory=dict, hash=True) + known_data: Dict[IP, Set[Data]] = field(default_factory=dict, hash=True) + known_networks: Set[Network] = field(default_factory=set, hash=True) + known_blocks: Dict[IP, Set[IP]] = field(default_factory=dict, hash=True) @property - def as_graph(self)->tuple: + def as_graph(self) -> Tuple[List[int], List[int], List[Tuple[int, int]], Dict[Any, int]]: """ Build a graph representation of the game state. Returns: - tuple: (node_features, controlled, edges, node_index_map) + Tuple[List[int], List[int], List[Tuple[int, int]], Dict[Any, int]]: + (node_features, controlled, edges, node_index_map) """ node_types = {"network":0, "host":1, "service":2, "datapoint":3, "blocks": 4} graph_nodes = {} @@ -629,29 +632,30 @@ def as_json(self) -> str: return json.dumps(ret_dict) @property - def as_dict(self)->dict: + def as_dict(self) -> Dict[str, Any]: """ Return the dictionary representation of the GameState. Returns: - dict: The game state as a dictionary. + Dict[str, Any]: The game state as a dictionary. """ - ret_dict = {"known_networks":[dataclasses.asdict(x) for x in self.known_networks], + ret_dict = { + "known_networks":[dataclasses.asdict(x) for x in self.known_networks], "known_hosts":[dataclasses.asdict(x) for x in self.known_hosts], "controlled_hosts":[dataclasses.asdict(x) for x in self.controlled_hosts], "known_services": {str(host):[dataclasses.asdict(s) for s in services] for host,services in self.known_services.items()}, "known_data":{str(host):[dataclasses.asdict(d) for d in data] for host,data in self.known_data.items()}, "known_blocks":{str(target_host):[dataclasses.asdict(blocked_host) for blocked_host in blocked_hosts] for target_host, blocked_hosts in self.known_blocks.items()} - } + } return ret_dict @classmethod - def from_dict(cls, data_dict:dict)->"GameState": + def from_dict(cls, data_dict: Dict[str, Any]) -> GameState: """ Create a GameState from a dictionary. Args: - data_dict (dict): The game state as a dictionary. + data_dict (Dict[str, Any]): The game state as a dictionary. Returns: GameState: The created GameState object. @@ -672,7 +676,7 @@ def from_dict(cls, data_dict:dict)->"GameState": return state @classmethod - def from_json(cls, json_string)->"GameState": + def from_json(cls, json_string: str) -> GameState: """ Create a GameState from a JSON string. @@ -695,16 +699,20 @@ def from_json(cls, json_string)->"GameState": return state -# Observation - given to agent after taking an action -""" -Observations are given when making a step in the environment. - - observation: current state of the environment - - reward: float value with immediate reward for last step - - end: boolean, True if the game ended. - No further interaction is possible (either terminal state or because of timeout) - - info: dict, can contain additional information about the reason for ending -""" -Observation = namedtuple("Observation", ["state", "reward", "end", "info"]) +class Observation(NamedTuple): + """ + Observations are given when making a step in the environment. + + Attributes: + state (GameState): Current state of the environment. + reward (float): Value with immediate reward for last step. + end (bool): True if the game ended. + info (Dict[str, Any]): Dictionary with additional information about the reason for ending. + """ + state: GameState + reward: float + end: bool + info: Dict[str, Any] @enum.unique class GameStatus(enum.Enum): @@ -719,7 +727,7 @@ class GameStatus(enum.Enum): FORBIDDEN = 403 @classmethod - def from_string(cls, string:str)->"GameStatus": + def from_string(cls, string: str) -> GameStatus: """ Convert a string to a GameStatus enum. @@ -742,6 +750,7 @@ def from_string(cls, string:str)->"GameStatus": return GameStatus.RESET_DONE case _: raise ValueError(f"Invalid GameStatus string: {string}") + def __repr__(self) -> str: """ Return the string representation of the GameStatus. @@ -764,7 +773,7 @@ class AgentStatus(enum.Enum): Success = "Success" Fail = "Fail" - def to_string(self)->str: + def to_string(self) -> str: """ Convert the AgentStatus enum to string. @@ -773,12 +782,12 @@ def to_string(self)->str: """ return self.value - def __eq__(self, other)->bool: + def __eq__(self, other: object) -> bool: """ Compare AgentStatus with another AgentStatus or string. Args: - other (AgentStatus or str): The object to compare. + other (object): The object to compare. Returns: bool: True if equal, False otherwise. @@ -791,7 +800,7 @@ def __eq__(self, other)->bool: return self.value == other.replace("AgentStatus.", "") return False - def __hash__(self)->int: + def __hash__(self) -> int: """ Compute the hash of the AgentStatus. @@ -802,7 +811,7 @@ def __hash__(self)->int: return hash(self.value) @classmethod - def from_string(cls, name)->"AgentStatus": + def from_string(cls, name: str) -> AgentStatus: """ Convert a string to an AgentStatus enum. @@ -831,5 +840,5 @@ class ProtocolConfig: END_OF_MESSAGE (bytes): End-of-message marker. BUFFER_SIZE (int): Buffer size for messages. """ - END_OF_MESSAGE = b"EOF" - BUFFER_SIZE = 8192 \ No newline at end of file + END_OF_MESSAGE: bytes = b"EOF" + BUFFER_SIZE: int = 8192 \ No newline at end of file From 8ef51b04e0bdc0181d73285a33565316263bf4a3 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 17:37:32 +0100 Subject: [PATCH 073/112] new set of tests --- netsecgame/game_components.py | 13 +---- tests/components/test_action.py | 45 +++++++++++++++- tests/components/test_enums.py | 72 +++++++++++++++++++++++++ tests/components/test_game_state.py | 83 ++++++++++++++++++++++++++++- tests/components/test_ip.py | 13 ++++- tests/components/test_network.py | 19 ++++++- 6 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 tests/components/test_enums.py diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index d80d00ba..25b95a07 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -686,17 +686,8 @@ def from_json(cls, json_string: str) -> GameState: Returns: GameState: The created GameState object. """ - json_data = json.loads(json_string) - state = GameState( - known_networks = {Network(x["ip"], x["mask"]) for x in json_data["known_networks"]}, - known_hosts = {IP(x["ip"]) for x in json_data["known_hosts"]}, - controlled_hosts = {IP(x["ip"]) for x in json_data["controlled_hosts"]}, - known_services = {IP(k):{Service(s["name"], s["type"], s["version"], s["is_local"]) - for s in services} for k,services in json_data["known_services"].items()}, - known_data = {IP(k):{Data(v["owner"], v["id"], v["size"], v["type"], v["content"]) for v in values} for k,values in json_data["known_data"].items()}, - known_blocks = {IP(target_host):{IP(blocked_host) for blocked_host in blocked_hosts} for target_host, blocked_hosts in json_data["known_blocks"].items()} - ) - return state + data_dict = json.loads(json_string) + return cls.from_dict(data_dict) class Observation(NamedTuple): diff --git a/tests/components/test_action.py b/tests/components/test_action.py index c0840003..5fa036b0 100644 --- a/tests/components/test_action.py +++ b/tests/components/test_action.py @@ -1,6 +1,7 @@ # Authors: Maria Rigaki - maria.rigaki@aic.fel.cvut.cz # Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz import json +import pytest from netsecgame.game_components import Action, ActionType, IP, Network, Data, Service, AgentInfo class TestComponentActionType: @@ -447,4 +448,46 @@ def test_action_to_dict_quit_game(self): new_action = Action.from_dict(action_dict) assert action == new_action assert action_dict["action_type"] == str(action.type) - assert len(action_dict["parameters"]) == 0 \ No newline at end of file + assert len(action_dict["parameters"]) == 0 + + def test_action_type_eq_unsupported(self): + """Test ActionType equality with unsupported type""" + assert (ActionType.FindData == 123) is False + + def test_action_type_from_string_invalid(self): + """Test ActionType.from_string with invalid string""" + with pytest.raises(ValueError): + ActionType.from_string("InvalidAction") + + def test_action_eq_unsupported(self): + """Test Action equality with unsupported type""" + action = Action(action_type=ActionType.FindData) + assert (action == "some_string") is False + + def test_action_from_dict_invalid_parameter(self): + """Test Action.from_dict with invalid parameter key""" + data = { + "action_type": "ActionType.FindData", + "parameters": {"unknown_param": "value"} + } + with pytest.raises(ValueError): + Action.from_dict(data) + + def test_action_to_dict_bool_parameter(self): + """Test handling of boolean parameters in as_dict""" + action = Action( + action_type=ActionType.ResetGame, + parameters={"request_trajectory": True} + ) + d = action.as_dict + assert d["parameters"]["request_trajectory"] is True + + def test_action_to_dict_str_parameter(self): + """Test handling of string parameters in as_dict""" + # Inject a parameter that is just a string (not a dataclass) + # We need a new ActionType or reuse one that accepts arbitrary params? + # The existing code mainly expects specific params. + # But we can force it for testing as_dict logic. + action = Action(ActionType.FindData, parameters={"simple_param": "simple_value"}) + d = action.as_dict + assert d["parameters"]["simple_param"] == "simple_value" \ No newline at end of file diff --git a/tests/components/test_enums.py b/tests/components/test_enums.py new file mode 100644 index 00000000..681e297a --- /dev/null +++ b/tests/components/test_enums.py @@ -0,0 +1,72 @@ +import pytest +from netsecgame.game_components import GameStatus, AgentStatus, Observation, GameState, ProtocolConfig + +class TestGameStatus: + def test_from_string_valid(self): + """Test valid from_string conversions""" + assert GameStatus.from_string("GameStatus.OK") == GameStatus.OK + assert GameStatus.from_string("GameStatus.CREATED") == GameStatus.CREATED + assert GameStatus.from_string("GameStatus.RESET_DONE") == GameStatus.RESET_DONE + assert GameStatus.from_string("GameStatus.BAD_REQUEST") == GameStatus.BAD_REQUEST + assert GameStatus.from_string("GameStatus.FORBIDDEN") == GameStatus.FORBIDDEN + + def test_from_string_invalid(self): + """Test invalid from_string conversion""" + with pytest.raises(ValueError): + GameStatus.from_string("GameStatus.INVALID") + + def test_repr(self): + """Test string representation""" + assert str(GameStatus.OK) == "GameStatus.OK" + assert repr(GameStatus.OK) == "GameStatus.OK" + +class TestAgentStatus: + def test_to_string(self): + """Test to_string method""" + assert AgentStatus.Playing.to_string() == "Playing" + assert AgentStatus.Success.to_string() == "Success" + + def test_eq_string(self): + """Test equality with string""" + assert AgentStatus.Playing == "Playing" + assert AgentStatus.Playing == "AgentStatus.Playing" + assert (AgentStatus.Playing == "Other") is False + + def test_eq_self(self): + """Test equality with self""" + assert AgentStatus.Playing == AgentStatus.Playing + assert (AgentStatus.Playing == AgentStatus.Success) is False + + def test_eq_other(self): + """Test equality with other types""" + assert (AgentStatus.Playing == 123) is False + + def test_hash(self): + """Test hash consistency""" + assert hash(AgentStatus.Playing) == hash(AgentStatus.Playing.value) + + def test_from_string(self): + """Test from_string method""" + assert AgentStatus.from_string("AgentStatus.Playing") == AgentStatus.Playing + assert AgentStatus.from_string("Playing") == AgentStatus.Playing + + with pytest.raises(ValueError): + AgentStatus.from_string("Invalid") + +class TestObservation: + def test_creation(self): + """Test creation of Observation named tuple""" + state = GameState() + obs = Observation(state=state, reward=1.0, end=False, info={}) + + assert obs.state == state + assert obs.reward == 1.0 + assert obs.end is False + assert obs.info == {} + +class TestProtocolConfig: + def test_constants(self): + """Test protocol constants""" + conf = ProtocolConfig() + assert conf.END_OF_MESSAGE == b"EOF" + assert conf.BUFFER_SIZE == 8192 diff --git a/tests/components/test_game_state.py b/tests/components/test_game_state.py index 2f92bdb9..6eb748a9 100644 --- a/tests/components/test_game_state.py +++ b/tests/components/test_game_state.py @@ -300,4 +300,85 @@ def test_game_state_from_dict(sample_ip, sample_ip2, sample_network, sample_serv game_dict = game_state.as_dict deserialized_state = GameState.from_dict(game_dict) assert game_state is not deserialized_state - assert game_state == deserialized_state \ No newline at end of file + assert game_state == deserialized_state + +def test_game_state_as_graph(sample_ip, sample_ip2, sample_network, sample_service, sample_data): + """Test as_graph method""" + game_state = GameState( + controlled_hosts={sample_ip}, + known_hosts={sample_ip, sample_ip2}, + known_services={sample_ip: {sample_service}}, + known_data={sample_ip: {sample_data}}, + known_networks={sample_network} + ) + + node_features, controlled, edges, node_index_map = game_state.as_graph + + # Check basic structure + assert isinstance(node_features, list) + assert isinstance(controlled, list) + assert isinstance(edges, list) + assert isinstance(node_index_map, dict) + + # Check node types mapping: network:0, host:1, service:2, datapoint:3, blocks:4 + # We expect: 1 network, 2 hosts, 1 service, 1 data point = 5 nodes + assert len(node_features) == 5 + assert len(controlled) == 5 + assert len(node_index_map) == 5 + + # Check specific nodes are present + assert sample_network in node_index_map.values() + assert sample_ip in node_index_map.values() + assert sample_ip2 in node_index_map.values() + assert sample_service in node_index_map.values() + assert sample_data in node_index_map.values() + + # Invert map for index lookup + obj_to_idx = {v: k for k, v in node_index_map.items()} + + # Check controlled status + ip1_idx = obj_to_idx[sample_ip] + ip2_idx = obj_to_idx[sample_ip2] + assert controlled[ip1_idx] == 1 + assert controlled[ip2_idx] == 0 + + # Check edges + # Host IP1 should be connected to Network (192.168.1.1 in 192.168.1.0/24) + # Host IP2 should be connected to Network + # Service on IP1 should be connected to IP1 + # Data on IP1 should be connected to IP1 + + net_idx = obj_to_idx[sample_network] + svc_idx = obj_to_idx[sample_service] + data_idx = obj_to_idx[sample_data] + + # Check edge existence (undirected, so double entries) + edge_set = set(edges) + assert (net_idx, ip1_idx) in edge_set + assert (ip1_idx, net_idx) in edge_set + assert (net_idx, ip2_idx) in edge_set + assert (ip1_idx, svc_idx) in edge_set + assert (ip1_idx, data_idx) in edge_set + +def test_game_state_known_blocks(sample_ip, sample_ip2): + """Test known_blocks handling""" + # Create state with blocks + blocks = {sample_ip: {sample_ip2}} + game_state = GameState(known_blocks=blocks) + + assert game_state.known_blocks == blocks + + # Test to dict + d = game_state.as_dict + assert "known_blocks" in d + # Expect: {'192.168.1.1': [{'ip': '192.168.1.2'}]} + assert d["known_blocks"][str(sample_ip)][0]["ip"] == str(sample_ip2) + + # Test from dict + new_state = GameState.from_dict(d) + assert new_state == game_state + + # Test to/from json + j = game_state.as_json() + new_state_json = GameState.from_json(j) + assert new_state_json == game_state \ No newline at end of file diff --git a/tests/components/test_ip.py b/tests/components/test_ip.py index df938357..b4846fbf 100644 --- a/tests/components/test_ip.py +++ b/tests/components/test_ip.py @@ -85,4 +85,15 @@ def test_ip_hash(sample_private_ip1, sample_private_ip1_copy): """Test that the hash of two IP objects with the same IP is equal""" ip_1, _ = sample_private_ip1 ip_2, _ = sample_private_ip1_copy - assert hash(ip_1) == hash(ip_2) \ No newline at end of file + assert hash(ip_1) == hash(ip_2) + +def test_ip_is_private_external(): + """Test is_private returns False for 'external' string""" + ip = IP("external") + assert ip.is_private() is False + +def test_ip_eq_other_type(sample_private_ip1): + """Test equality with non-IP object""" + ip_1, _ = sample_private_ip1 + assert ip_1.__eq__("some_string") is NotImplemented + assert (ip_1 == "some_string") is False # Python fallback \ No newline at end of file diff --git a/tests/components/test_network.py b/tests/components/test_network.py index d11f654a..d12cfdc1 100644 --- a/tests/components/test_network.py +++ b/tests/components/test_network.py @@ -80,4 +80,21 @@ def test_net_from_dict(sample_private_network1): assert net.ip == "192.168.1.0" assert net.mask == 24 assert net == sample_private_network1 - assert net is not sample_private_network1 \ No newline at end of file + assert net is not sample_private_network1 + +def test_net_less_than(sample_private_network1, sample_private_network2): + """Test __lt__ operator""" + # 192.168.1.0/24 < 192.168.2.0/24 + assert sample_private_network1 < sample_private_network2 + assert not (sample_private_network2 < sample_private_network1) + +def test_net_less_than_equal(sample_private_network1, sample_private_network2, sample_private_network1_copy): + """Test __le__ operator""" + assert sample_private_network1 <= sample_private_network2 + assert sample_private_network1 <= sample_private_network1_copy + assert not (sample_private_network2 <= sample_private_network1) + +def test_net_greater_than(sample_private_network1, sample_private_network2): + """Test __gt__ operator""" + assert sample_private_network2 > sample_private_network1 + assert not (sample_private_network1 > sample_private_network2) \ No newline at end of file From a6ae2e25b6708b8f4736cd2b4f6dd4fbbbdd4e42 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 17:45:25 +0100 Subject: [PATCH 074/112] Fixed deserialization to be robust against unexpected dictionary wrappings --- netsecgame/game_components.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index 25b95a07..d94ae796 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -661,7 +661,15 @@ def from_dict(cls, data_dict: Dict[str, Any]) -> GameState: GameState: The created GameState object. """ if "known_blocks" in data_dict: - known_blocks = {IP(target_host):{IP(blocked_host["ip"]) for blocked_host in blocked_hosts} for target_host, blocked_hosts in data_dict["known_blocks"].items()} + known_blocks = {} + for target_host, blocked_hosts in data_dict["known_blocks"].items(): + blocked_ips = set() + for blocked_host in blocked_hosts: + ip_val = blocked_host["ip"] + if isinstance(ip_val, dict): + ip_val = ip_val["ip"] + blocked_ips.add(IP(ip_val)) + known_blocks[IP(target_host)] = blocked_ips else: known_blocks = {} state = GameState( From edcf6ce637f819fb82a343c2fa7fcab728399845 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 17:46:06 +0100 Subject: [PATCH 075/112] remove redundant test --- tests/components/test_ip.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/components/test_ip.py b/tests/components/test_ip.py index b4846fbf..b72dda5f 100644 --- a/tests/components/test_ip.py +++ b/tests/components/test_ip.py @@ -87,11 +87,6 @@ def test_ip_hash(sample_private_ip1, sample_private_ip1_copy): ip_2, _ = sample_private_ip1_copy assert hash(ip_1) == hash(ip_2) -def test_ip_is_private_external(): - """Test is_private returns False for 'external' string""" - ip = IP("external") - assert ip.is_private() is False - def test_ip_eq_other_type(sample_private_ip1): """Test equality with non-IP object""" ip_1, _ = sample_private_ip1 From 5951b0b0d33097c1322cf46db670944c0ebf5ecc Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 17:59:50 +0100 Subject: [PATCH 076/112] Separate agent server --- netsecgame/game/agent_server.py | 124 +++++++++++++++++++++++++++++++ netsecgame/game/coordinator.py | 125 +------------------------------- 2 files changed, 127 insertions(+), 122 deletions(-) create mode 100644 netsecgame/game/agent_server.py diff --git a/netsecgame/game/agent_server.py b/netsecgame/game/agent_server.py new file mode 100644 index 00000000..550ae9e0 --- /dev/null +++ b/netsecgame/game/agent_server.py @@ -0,0 +1,124 @@ +import logging +import asyncio +from netsecgame.game_components import Action, ActionType, ProtocolConfig + +class AgentServer(asyncio.Protocol): + """ + Class used for serving the agents when connecting to the game run by the GameCoordinator. + + Attributes: + actions_queue (asyncio.Queue): Queue for actions from agents. + answers_queues (dict): Mapping of agent addresses to their response queues. + max_connections (int): Maximum allowed concurrent agent connections. + current_connections (int): Current number of connected agents. + logger (logging.Logger): Logger for the AgentServer. + """ + def __init__(self, actions_queue, agent_response_queues, max_connections): + """ + Initialize the AgentServer. + + Args: + actions_queue (asyncio.Queue): Queue for actions from agents. + agent_response_queues (dict): Mapping of agent addresses to their response queues. + max_connections (int): Maximum allowed concurrent agent connections. + """ + self.actions_queue = actions_queue + self.answers_queues = agent_response_queues + self.max_connections = max_connections + self.current_connections = 0 + self.logger = logging.getLogger("AIDojo-AgentServer") + + async def handle_agent_quit(self, peername:tuple): + """ + Helper function to handle agent disconnection. + + Args: + peername (tuple): The address of the disconnecting agent. + """ + # Send a quit message to the Coordinator + self.logger.info(f"\tHandling agent quit for {peername}.") + quit_message = Action(ActionType.QuitGame, parameters={}).to_json() + await self.actions_queue.put((peername, quit_message)) + + async def handle_new_agent(self, reader, writer): + """ + Handle a new agent connection. + + Args: + reader (asyncio.StreamReader): Stream reader for the agent. + writer (asyncio.StreamWriter): Stream writer for the agent. + """ + # get the peername of the writer + peername = writer.get_extra_info("peername") + queue_created = False + try: + self.logger.info(f"New connection from {peername}") + # Check if the maximum number of connections has been reached + if self.current_connections < self.max_connections: + # increment the count of current connections + self.current_connections += 1 + self.logger.info(f"New agent connected: {peername}. Current connections: {self.current_connections}") + # Ensure a queue exists for this agent + if peername not in self.answers_queues: + self.answers_queues[peername] = asyncio.Queue(maxsize=2) + queue_created = True + self.logger.info(f"Created queue for agent {peername}") + # Handle the new agent + while True: + # Step 1: Read data from the agent + data = await reader.read(ProtocolConfig.BUFFER_SIZE) + if not data: + self.logger.info(f"Agent {peername} disconnected.") + await self.handle_agent_quit(peername) + break + + raw_message = data.decode().strip() + self.logger.debug(f"Handler received from {peername}: {raw_message}") + + # Step 2: Forward the message to the Coordinator + await self.actions_queue.put((peername, raw_message)) + + # Step 3: Get a matching response from the answers queue + response_queue = self.answers_queues[peername] + response = await response_queue.get() + self.logger.info(f"Sending response to agent {peername}: {response}") + + # Step 4: Send the response to the agent + response = str(response).encode() + ProtocolConfig.END_OF_MESSAGE + writer.write(response) + await writer.drain() + else: + self.logger.warning(f"Queue for agent {peername} already exists. Closing connection.") + else: + self.logger.info(f"Max connections reached. Rejecting new connection from {writer.get_extra_info('peername')}") + except ConnectionResetError: + self.logger.warning(f"Connection reset by {peername}") + await self.handle_agent_quit(peername) + except asyncio.CancelledError: + self.logger.debug("Connection handling cancelled.") + raise # Ensure the exception propagates + except Exception as e: + self.logger.error(f"Unexpected error with client {peername}: {e}") + raise + finally: + try: + if peername in self.answers_queues: + # If the queue was created, remove it + if queue_created: + self.answers_queues.pop(peername) + self.logger.info(f"Removed queue for agent {peername}") + self.current_connections = max(0, self.current_connections - 1) + writer.close() + await writer.wait_closed() + except Exception: + # swallow exceptions on close to avoid crash on cleanup + pass + async def __call__(self, reader, writer): + """ + Allow the server instance to be called as a coroutine. + + Args: + reader (asyncio.StreamReader): Stream reader for the agent. + writer (asyncio.StreamWriter): Stream writer for the agent. + """ + await self.handle_new_agent(reader, writer) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 28b39196..71b8238d 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -3,135 +3,16 @@ import asyncio from datetime import datetime import signal +import os +from aiohttp import ClientSession from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig from netsecgame.game.global_defender import GlobalDefender from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl from netsecgame.game.config_parser import ConfigParser -import os -from aiohttp import ClientSession +from netsecgame.game.agent_server import AgentServer from cyst.api.environment.environment import Environment -class AgentServer(asyncio.Protocol): - """ - Class used for serving the agents when connecting to the game run by the GameCoordinator. - - Attributes: - actions_queue (asyncio.Queue): Queue for actions from agents. - answers_queues (dict): Mapping of agent addresses to their response queues. - max_connections (int): Maximum allowed concurrent agent connections. - current_connections (int): Current number of connected agents. - logger (logging.Logger): Logger for the AgentServer. - """ - def __init__(self, actions_queue, agent_response_queues, max_connections): - """ - Initialize the AgentServer. - - Args: - actions_queue (asyncio.Queue): Queue for actions from agents. - agent_response_queues (dict): Mapping of agent addresses to their response queues. - max_connections (int): Maximum allowed concurrent agent connections. - """ - self.actions_queue = actions_queue - self.answers_queues = agent_response_queues - self.max_connections = max_connections - self.current_connections = 0 - self.logger = logging.getLogger("AIDojo-AgentServer") - - async def handle_agent_quit(self, peername:tuple): - """ - Helper function to handle agent disconnection. - - Args: - peername (tuple): The address of the disconnecting agent. - """ - # Send a quit message to the Coordinator - self.logger.info(f"\tHandling agent quit for {peername}.") - quit_message = Action(ActionType.QuitGame, parameters={}).to_json() - await self.actions_queue.put((peername, quit_message)) - - async def handle_new_agent(self, reader, writer): - """ - Handle a new agent connection. - - Args: - reader (asyncio.StreamReader): Stream reader for the agent. - writer (asyncio.StreamWriter): Stream writer for the agent. - """ - # get the peername of the writer - peername = writer.get_extra_info("peername") - queue_created = False - try: - self.logger.info(f"New connection from {peername}") - # Check if the maximum number of connections has been reached - if self.current_connections < self.max_connections: - # increment the count of current connections - self.current_connections += 1 - self.logger.info(f"New agent connected: {peername}. Current connections: {self.current_connections}") - # Ensure a queue exists for this agent - if peername not in self.answers_queues: - self.answers_queues[peername] = asyncio.Queue(maxsize=2) - queue_created = True - self.logger.info(f"Created queue for agent {peername}") - # Handle the new agent - while True: - # Step 1: Read data from the agent - data = await reader.read(ProtocolConfig.BUFFER_SIZE) - if not data: - self.logger.info(f"Agent {peername} disconnected.") - await self.handle_agent_quit(peername) - break - - raw_message = data.decode().strip() - self.logger.debug(f"Handler received from {peername}: {raw_message}") - - # Step 2: Forward the message to the Coordinator - await self.actions_queue.put((peername, raw_message)) - - # Step 3: Get a matching response from the answers queue - response_queue = self.answers_queues[peername] - response = await response_queue.get() - self.logger.info(f"Sending response to agent {peername}: {response}") - - # Step 4: Send the response to the agent - response = str(response).encode() + ProtocolConfig.END_OF_MESSAGE - writer.write(response) - await writer.drain() - else: - self.logger.warning(f"Queue for agent {peername} already exists. Closing connection.") - else: - self.logger.info(f"Max connections reached. Rejecting new connection from {writer.get_extra_info('peername')}") - except ConnectionResetError: - self.logger.warning(f"Connection reset by {peername}") - await self.handle_agent_quit(peername) - except asyncio.CancelledError: - self.logger.debug("Connection handling cancelled.") - raise # Ensure the exception propagates - except Exception as e: - self.logger.error(f"Unexpected error with client {peername}: {e}") - raise - finally: - try: - if peername in self.answers_queues: - # If the queue was created, remove it - if queue_created: - self.answers_queues.pop(peername) - self.logger.info(f"Removed queue for agent {peername}") - self.current_connections = max(0, self.current_connections - 1) - writer.close() - await writer.wait_closed() - except Exception: - # swallow exceptions on close to avoid crash on cleanup - pass - async def __call__(self, reader, writer): - """ - Allow the server instance to be called as a coroutine. - - Args: - reader (asyncio.StreamReader): Stream reader for the agent. - writer (asyncio.StreamWriter): Stream writer for the agent. - """ - await self.handle_new_agent(reader, writer) class GameCoordinator: """ From 6154debf7f2437b3298b88532b76f5676d411fcc Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 22:59:08 +0100 Subject: [PATCH 077/112] Split and expose trajectory recorderd from coordinator --- netsecgame/__init__.py | 7 +- netsecgame/utils/trajectory_recorder.py | 82 +++++++++++++++++ tests/coordinator/test_trajectory_recorder.py | 88 +++++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 netsecgame/utils/trajectory_recorder.py create mode 100644 tests/coordinator/test_trajectory_recorder.py diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index ab241cd8..45793a3e 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -33,6 +33,9 @@ generate_valid_actions ) +# Trajectory Recorder +from .utils.trajectory_recorder import TrajectoryRecorder + # Define the public API of the package __all__ = [ # Metadata @@ -60,5 +63,7 @@ "observation_to_str", "observation_from_str", "observation_from_dict", - "generate_valid_actions" + "generate_valid_actions", + # Trajectory recorder + "TrajectoryRecorder" ] \ No newline at end of file diff --git a/netsecgame/utils/trajectory_recorder.py b/netsecgame/utils/trajectory_recorder.py new file mode 100644 index 00000000..00bacee0 --- /dev/null +++ b/netsecgame/utils/trajectory_recorder.py @@ -0,0 +1,82 @@ +# trajectory_recorder.py +# Author: Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz +import os +import logging +from datetime import datetime +from typing import Optional, Dict, Any, List +from netsecgame.game_components import Action, GameState +from netsecgame.utils.utils import store_trajectories_to_jsonl + +class TrajectoryRecorder: + """ + Manages the recording and storage of agent trajectories. + """ + def __init__(self, agent_name: str, agent_role: str): + self.agent_name = agent_name + self.agent_role = agent_role + self.logger = logging.getLogger(f"TrajectoryRecorder-{agent_name}") + self._data: Dict[str, Any] = {} + self.reset() + + def reset(self) -> None: + """ + Resets the trajectory data for a new episode. + """ + self.logger.debug(f"Resetting trajectory for {self.agent_name}") + self._data = { + "trajectory": { + "states": [], + "actions": [], + "rewards": [], + }, + "end_reason": None, + "agent_role": self.agent_role, + "agent_name": self.agent_name + } + + def add_step(self, action: Action, reward: float, next_state: GameState, end_reason: Optional[str] = None) -> None: + """ + Adds a single step to the trajectory. + + Args: + action (Action): The action taken. + reward (float): The reward received. + next_state (GameState): The resulting state. + end_reason (Optional[str]): Reason for episode end, if applicable. + """ + self.logger.debug(f"Adding step to trajectory for {self.agent_name}") + # Assuming Action and GameState have .as_dict property or method as in original code + # In original code: action.as_dict, next_state.as_dict + self._data["trajectory"]["actions"].append(action.as_dict) + self._data["trajectory"]["rewards"].append(reward) + self._data["trajectory"]["states"].append(next_state.as_dict) + + if end_reason: + self._data["end_reason"] = end_reason + + def add_initial_state(self, state: GameState) -> None: + """ + Adds the initial state to the trajectory (optional, depending on how you want to track s_0). + The original code initialized trajectory with states=[agent_state.as_dict]. + """ + self._data["trajectory"]["states"].append(state.as_dict) + + def get_trajectory(self) -> Dict[str, Any]: + """ + Returns the current trajectory data. + """ + return self._data + + def save_to_file(self, location: str = "./logs/trajectories") -> None: + """ + Saves the recorded trajectory to a JSONL file. + + Args: + location (str): Directory to save the file. + """ + filename = f"{datetime.now():%Y-%m-%d}_{self.agent_name}_{self.agent_role}" + try: + store_trajectories_to_jsonl(self._data, location, filename) + self.logger.info(f"Trajectory stored in {os.path.join(location, filename)}.jsonl") + except Exception as e: + self.logger.error(f"Failed to store trajectory: {e}") diff --git a/tests/coordinator/test_trajectory_recorder.py b/tests/coordinator/test_trajectory_recorder.py new file mode 100644 index 00000000..7d281cbd --- /dev/null +++ b/tests/coordinator/test_trajectory_recorder.py @@ -0,0 +1,88 @@ +import pytest +from unittest.mock import MagicMock, patch +from netsecgame import TrajectoryRecorder +from netsecgame.game_components import Action, ActionType, GameState, Network + +# Mock objects needed for tests +@pytest.fixture +def mock_action(): + return Action(ActionType.ScanNetwork, parameters={"target": "10.0.0.1"}) + +@pytest.fixture +def mock_gamestate(): + # Minimal GameState for testing + return GameState( + controlled_hosts=set(), + known_hosts=set(), + known_services={}, + known_data={}, + known_networks=set() + ) + +@pytest.fixture +def recorder(): + return TrajectoryRecorder(agent_name="test_agent", agent_role="Attacker") + +def test_initialization(recorder): + assert recorder.agent_name == "test_agent" + assert recorder.agent_role == "Attacker" + data = recorder.get_trajectory() + assert data["agent_name"] == "test_agent" + assert data["agent_role"] == "Attacker" + assert data["trajectory"]["states"] == [] + assert data["trajectory"]["actions"] == [] + assert data["trajectory"]["rewards"] == [] + assert data["end_reason"] is None + +def test_add_initial_state(recorder, mock_gamestate): + recorder.add_initial_state(mock_gamestate) + data = recorder.get_trajectory() + assert len(data["trajectory"]["states"]) == 1 + assert data["trajectory"]["states"][0] == mock_gamestate.as_dict + +def test_add_step(recorder, mock_action, mock_gamestate): + recorder.add_step(mock_action, reward=10.0, next_state=mock_gamestate, end_reason=None) + data = recorder.get_trajectory() + + assert len(data["trajectory"]["actions"]) == 1 + assert data["trajectory"]["actions"][0] == mock_action.as_dict + + assert len(data["trajectory"]["rewards"]) == 1 + assert data["trajectory"]["rewards"][0] == 10.0 + + assert len(data["trajectory"]["states"]) == 1 + assert data["trajectory"]["states"][0] == mock_gamestate.as_dict + + assert data["end_reason"] is None + +def test_add_step_with_end_reason(recorder, mock_action, mock_gamestate): + recorder.add_step(mock_action, reward=0, next_state=mock_gamestate, end_reason="Timeout") + data = recorder.get_trajectory() + assert data["end_reason"] == "Timeout" + +def test_reset(recorder, mock_action, mock_gamestate): + recorder.add_step(mock_action, 10, mock_gamestate) + recorder.reset() + data = recorder.get_trajectory() + + assert data["trajectory"]["states"] == [] + assert data["trajectory"]["actions"] == [] + assert data["trajectory"]["rewards"] == [] + assert data["end_reason"] is None + assert data["agent_name"] == "test_agent" + +@patch("netsecgame.utils.trajectory_recorder.store_trajectories_to_jsonl") +def test_save_to_file(mock_store, recorder): + recorder.save_to_file(location="/tmp/logs") + + # Check if called with correct args + mock_store.assert_called_once() + args, _ = mock_store.call_args + + saved_data = args[0] + location = args[1] + filename = args[2] + + assert saved_data == recorder.get_trajectory() + assert location == "/tmp/logs" + assert "test_agent_Attacker" in filename From 5af12ff66bc0b45e45e08cf28a2babdc9bef9cb0 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 23:04:32 +0100 Subject: [PATCH 078/112] Split the tests --- tests/{coordinator => utils}/test_trajectory_recorder.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{coordinator => utils}/test_trajectory_recorder.py (100%) diff --git a/tests/coordinator/test_trajectory_recorder.py b/tests/utils/test_trajectory_recorder.py similarity index 100% rename from tests/coordinator/test_trajectory_recorder.py rename to tests/utils/test_trajectory_recorder.py From ed7a9fa3a6269944b8069635f58a5b4669fab350 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 23:08:07 +0100 Subject: [PATCH 079/112] add tests for util functions --- tests/utils/test_utils.py | 131 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/utils/test_utils.py diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py new file mode 100644 index 00000000..7bc2fb93 --- /dev/null +++ b/tests/utils/test_utils.py @@ -0,0 +1,131 @@ +import pytest +import json +import logging +from unittest.mock import MagicMock, patch +from netsecgame.utils.utils import ( + get_str_hash, + state_as_ordered_string, + observation_as_dict, + observation_to_str, + observation_from_dict, + observation_from_str, + parse_log_content, + get_logging_level, + generate_valid_actions +) +from netsecgame.game_components import ( + GameState, + Observation, + Action, + ActionType, + IP, + Network, + Service, + Data +) + +# --- Fixtures --- + +@pytest.fixture +def sample_gamestate(): + net1 = Network("10.0.0.0", 24) + host1 = IP("10.0.0.1") + host2 = IP("10.0.0.2") + service1 = Service("http", "tcp", "80", False) + data1 = Data("root", "secret", "file", 100) + + return GameState( + controlled_hosts={host1}, + known_hosts={host1, host2}, + known_services={host2: {service1}}, + known_data={host1: {data1}}, + known_networks={net1}, + known_blocks={host1: {host2}} + ) + +@pytest.fixture +def sample_observation(sample_gamestate): + return Observation( + state=sample_gamestate, + reward=10.0, + end=False, + info={"reason": "test"} + ) + +# --- Tests --- + +def test_get_str_hash(): + s = "hello world" + # sha256 of "hello world" + expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + assert get_str_hash(s) == expected + +def test_state_as_ordered_string(sample_gamestate): + # This function produces a specific string format. + # We verify it contains expected substrings and is deterministic. + s1 = state_as_ordered_string(sample_gamestate) + s2 = state_as_ordered_string(sample_gamestate) + assert s1 == s2 + assert "nets:[10.0.0.0/24]" in s1 + assert "hosts:[10.0.0.1,10.0.0.2]" in s1 + assert "services:{10.0.0.2:[Service(name='http', type='tcp', version='80', is_local=False)]}" in s1 + +def test_observation_conversion_roundtrip(sample_observation): + # dict conversion + obs_dict = observation_as_dict(sample_observation) + assert obs_dict["reward"] == 10.0 + assert obs_dict["end"] is False + assert obs_dict["info"]["reason"] == "test" + + # restore from dict + obs_restored = observation_from_dict(obs_dict) + assert obs_restored.reward == sample_observation.reward + assert obs_restored.end == sample_observation.end + assert obs_restored.info == sample_observation.info + # State equality depends on GameState equality implementation + assert obs_restored.state.known_hosts == sample_observation.state.known_hosts + +def test_observation_json_roundtrip(sample_observation): + # str conversion + json_str = observation_to_str(sample_observation) + assert isinstance(json_str, str) + + # restore from str + obs_restored = observation_from_str(json_str) + assert obs_restored.reward == sample_observation.reward + assert obs_restored.end == sample_observation.end + assert obs_restored.state.known_hosts == sample_observation.state.known_hosts + +def test_observation_from_dict_error(): + # Invalid input + with pytest.raises(Exception): + observation_from_dict({"reward": 10}) # missing state + +def test_observation_from_str_error(): + with pytest.raises(Exception): + observation_from_str("invalid json") + +def test_parse_log_content(): + log_json = '[{"source_host": "10.0.0.1", "action_type": "ScanNetwork"}]' + logs = parse_log_content(log_json) + assert len(logs) == 1 + assert logs[0]["source_host"] == IP("10.0.0.1") + assert logs[0]["action_type"] == ActionType.ScanNetwork + +def test_parse_log_content_invalid(): + assert parse_log_content("invalid json") is None + +def test_get_logging_level(): + assert get_logging_level("DEBUG") == logging.DEBUG + assert get_logging_level("info") == logging.INFO + assert get_logging_level("UNKNOWN") == logging.ERROR + +def test_generate_valid_actions(sample_gamestate): + actions = generate_valid_actions(sample_gamestate, include_blocks=True) + assert isinstance(actions, list) + assert len(actions) > 0 + # Check for specific expected actions based on sample state + # Controlled host is 10.0.0.1 + # It should be able to ScanNetwork 10.0.0.0/24 + scan_actions = [a for a in actions if a.type == ActionType.ScanNetwork] + assert any(a.parameters["target_network"] == Network("10.0.0.0", 24) for a in scan_actions) From 4f6e55c35fcd4966807fcb357507588216225238 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Fri, 23 Jan 2026 23:10:49 +0100 Subject: [PATCH 080/112] rename test folder --- tests/{coordinator => game}/test_agent_server.py | 0 tests/{coordinator => game}/test_coordinator_core.py | 0 tests/{coordinator => game}/test_global_defender.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/{coordinator => game}/test_agent_server.py (100%) rename tests/{coordinator => game}/test_coordinator_core.py (100%) rename tests/{coordinator => game}/test_global_defender.py (100%) diff --git a/tests/coordinator/test_agent_server.py b/tests/game/test_agent_server.py similarity index 100% rename from tests/coordinator/test_agent_server.py rename to tests/game/test_agent_server.py diff --git a/tests/coordinator/test_coordinator_core.py b/tests/game/test_coordinator_core.py similarity index 100% rename from tests/coordinator/test_coordinator_core.py rename to tests/game/test_coordinator_core.py diff --git a/tests/coordinator/test_global_defender.py b/tests/game/test_global_defender.py similarity index 100% rename from tests/coordinator/test_global_defender.py rename to tests/game/test_global_defender.py From 0f5e9cb8dc18bf9dd5dc26c1e0a73962654ae279 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 11:52:42 +0100 Subject: [PATCH 081/112] Add Agent role as enum (remove str) --- netsecgame/game_components.py | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index d94ae796..e4db6a6c 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -830,6 +830,64 @@ def from_string(cls, name: str) -> AgentStatus: except KeyError: raise ValueError(f"Invalid AgentStatus: {name}") +@enum.unique +class AgentRole(enum.Enum): + """ + Enum representing possible roles of agents. + """ + Attacker = "Attacker" + Defender = "Defender" + Benign = "Benign" + + def to_string(self) -> str: + """ + Convert the AgentRole enum to string. + + Returns: + str: The string representation. + """ + return self.value + + def __eq__(self, other: object) -> bool: + """ + Compare AgentRole with another AgentRole or string. + + Args: + other (object): The object to compare. + + Returns: + bool: True if equal, False otherwise. + """ + if isinstance(other, AgentRole): + return self.value == other.value + elif isinstance(other, str): + return self.value.lower() == other.lower().replace("agentrole.", "") + return False + + @classmethod + def from_string(cls, name: str) -> AgentRole: + """ + Convert a string to an AgentRole enum. + + Args: + name (str): The string representation. + + Returns: + AgentRole: The corresponding AgentRole. + + Raises: + ValueError: If the string does not match any AgentRole. + """ + # Clean up input string + name = name.split(".")[-1] # Remove prefix if present + + # Try case-insensitive matching + for role in cls: + if role.value.lower() == name.lower(): + return role + + raise ValueError(f"Invalid AgentRole: {name}") + @dataclass(frozen=True) class ProtocolConfig: """ @@ -840,4 +898,5 @@ class ProtocolConfig: BUFFER_SIZE (int): Buffer size for messages. """ END_OF_MESSAGE: bytes = b"EOF" - BUFFER_SIZE: int = 8192 \ No newline at end of file + BUFFER_SIZE: int = 8192 + From e9dd52a8faa558fc2d4dc6a5f0f84e7dfc5dfa72 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 12:30:34 +0100 Subject: [PATCH 082/112] restric agent roles with enum --- netsecgame/agents/base_agent.py | 6 +++--- netsecgame/game_components.py | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/netsecgame/agents/base_agent.py b/netsecgame/agents/base_agent.py index 1569c054..0c5ad189 100644 --- a/netsecgame/agents/base_agent.py +++ b/netsecgame/agents/base_agent.py @@ -5,7 +5,7 @@ import json from abc import ABC -from netsecgame.game_components import Action, GameState, Observation, ActionType, GameStatus, AgentInfo, ProtocolConfig +from netsecgame.game_components import Action, GameState, Observation, ActionType, GameStatus, AgentInfo, ProtocolConfig, AgentRole class BaseAgent(ABC): """ @@ -152,7 +152,7 @@ def register(self)->Observation | None: try: self._logger.info(f'Registering agent as {self.role}') status, observation_dict, message = self.communicate(Action(ActionType.JoinGame, - parameters={"agent_info":AgentInfo(self.__class__.__name__,self.role)})) + parameters={"agent_info":AgentInfo(self.__class__.__name__,self.role.value)})) if status is GameStatus.CREATED: self._logger.info(f"\tRegistration successful! {message}") return Observation(GameState.from_dict(observation_dict["state"]), observation_dict["reward"], observation_dict["end"], message) @@ -184,7 +184,7 @@ def request_game_reset(self, request_trajectory=False, randomize_topology=True, if __name__ == "__main__": # Example usage of BaseAgent GAME_PORT = 5000 # Change to the appropriate port - agent = BaseAgent("localhost", GAME_PORT, "Attacker") + agent = BaseAgent("localhost", GAME_PORT, AgentRole.Attacker) # Register the agent observation = agent.register() if observation: diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index e4db6a6c..ac97b0a1 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -373,7 +373,10 @@ def from_dict(cls, data: Dict[str, Any]) -> AgentInfo: Returns: AgentInfo: The created AgentInfo object. """ - return cls(**data) + if isinstance(data, str): + data = ast.literal_eval(data) + processed = {"name": data["name"], "role": AgentRole.from_string(data["role"])} + return cls(**processed) @dataclass(frozen=True, slots=True) class Action: @@ -400,6 +403,8 @@ def as_dict(self) -> Dict[str, Any]: # Check if v is a dataclass AND ensuring it is an instance, not the class itself if dataclasses.is_dataclass(v) and not isinstance(v, type): params[k] = asdict(v) + elif isinstance(v, dict): + params[k] = v elif isinstance(v, bool): # Handle boolean values params[k] = v else: @@ -831,7 +836,7 @@ def from_string(cls, name: str) -> AgentStatus: raise ValueError(f"Invalid AgentStatus: {name}") @enum.unique -class AgentRole(enum.Enum): +class AgentRole(str, enum.Enum): """ Enum representing possible roles of agents. """ @@ -839,6 +844,15 @@ class AgentRole(enum.Enum): Defender = "Defender" Benign = "Benign" + def __repr__(self) -> str: + """ + Return the string representation of the AgentRole. + + Returns: + str: The agent role as a string. + """ + return self.value + def to_string(self) -> str: """ Convert the AgentRole enum to string. @@ -900,3 +914,10 @@ class ProtocolConfig: END_OF_MESSAGE: bytes = b"EOF" BUFFER_SIZE: int = 8192 +if __name__ == "__main__": + role_str = AgentRole.Attacker.to_string() + role = AgentRole.from_string(role_str) + action = Action(ActionType.JoinGame, parameters={"agent_info": {"role": role, "name": "TestAgent"}}) + print(action) + print(action.to_json()) + print(action.from_json(action.to_json())) \ No newline at end of file From b4a0b789609a5c5133a822a21dbd998f758f9674 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 12:37:09 +0100 Subject: [PATCH 083/112] Use AgentRole enum --- netsecgame/game/coordinator.py | 16 ++++++++-------- netsecgame/game/worlds/NetSecGame.py | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 71b8238d..fa0fa4b8 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -6,7 +6,7 @@ import os from aiohttp import ClientSession -from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig +from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig, AgentRole from netsecgame.game.global_defender import GlobalDefender from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl from netsecgame.game.config_parser import ConfigParser @@ -187,7 +187,7 @@ def _get_starting_position_per_role(self)->dict: Method for finding starting position for each agent role in the game. """ starting_positions = {} - for agent_role in self.ALLOWED_ROLES: + for agent_role in AgentRole: try: starting_positions[agent_role] = self.task_config.get_start_position(agent_role=agent_role) self.logger.info(f"Starting position for role '{agent_role}': {starting_positions[agent_role]}") @@ -200,7 +200,7 @@ def _get_win_condition_per_role(self)-> dict: Method for finding wininng conditions for each agent role in the game. """ win_conditions = {} - for agent_role in self.ALLOWED_ROLES: + for agent_role in AgentRole: try: win_conditions[agent_role] = self.task_config.get_win_conditions(agent_role=agent_role) except KeyError: @@ -213,7 +213,7 @@ def _get_goal_description_per_role(self)->dict: Method for finding goal description for each agent role in the game. """ goal_descriptions ={} - for agent_role in self.ALLOWED_ROLES: + for agent_role in AgentRole: try: goal_descriptions[agent_role] = self.task_config.get_goal_description(agent_role=agent_role) except KeyError: @@ -225,7 +225,7 @@ def _get_max_steps_per_role(self)->dict: """ Method for finding max amount of steps in 1 episode for each agent role in the game. """ - max_steps = {role:self.task_config.get_max_steps(role) for role in self.ALLOWED_ROLES} + max_steps = {role:self.task_config.get_max_steps(role) for role in AgentRole} return max_steps async def start_tcp_server(self): @@ -681,7 +681,7 @@ def _initialize_new_player(self, agent_addr:tuple, agent_current_state:GameState self._agent_last_action[agent_addr] = None self._agent_rewards[agent_addr] = 0 self._agent_false_positives[agent_addr] = 0 - if agent_role.lower() == "attacker": + if agent_role in [AgentRole.Attacker]: self._agent_status[agent_addr] = AgentStatus.PlayingWithTimeout else: self._agent_status[agent_addr] = AgentStatus.Playing @@ -690,7 +690,7 @@ def _initialize_new_player(self, agent_addr:tuple, agent_current_state:GameState # create initial observation return Observation(self._agent_states[agent_addr], 0, False, {}) - async def register_agent(self, agent_id:tuple, agent_role:str, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: + async def register_agent(self, agent_id:tuple, agent_role:AgentRole, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: """ Domain specific method of the environment. Creates the initial state of the agent. """ @@ -702,7 +702,7 @@ async def remove_agent(self, agent_id:tuple, agent_state:GameState)->bool: """ raise NotImplementedError - async def reset_agent(self, agent_id:tuple, agent_role:str, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: + async def reset_agent(self, agent_id:tuple, agent_role:AgentRole, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: raise NotImplementedError async def _remove_agent_from_game(self, agent_addr): diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index eda1a3a7..d8953406 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -12,7 +12,7 @@ from typing import Iterable, Any from collections import defaultdict -from netsecgame.game_components import GameState, Action, ActionType, IP, Network, Data, Service +from netsecgame.game_components import GameState, Action, ActionType, IP, Network, Data, Service, AgentRole from netsecgame.game.coordinator import GameCoordinator from cyst.api.configuration import NodeConfig, RouterConfig, ConnectionConfig, ExploitConfig, FirewallPolicy @@ -1060,7 +1060,7 @@ def update_log_file(self, known_data:set, action, target_host:IP): new_content = json.dumps(new_content) self._data[hostaname].add(Data(owner="system", id="logfile", type="log", size=len(new_content) , content= new_content)) - async def register_agent(self, agent_id, agent_role, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: + async def register_agent(self, agent_id, agent_role:AgentRole, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: start_game_state = self._create_state_from_view(agent_initial_view) goal_state = self._create_goal_state_from_view(agent_win_condition_view) return start_game_state, goal_state @@ -1072,7 +1072,7 @@ async def remove_agent(self, agent_id, agent_state)->bool: async def step(self, agent_id, agent_state, action)->GameState: return self._execute_action(agent_state, action, agent_id) - async def reset_agent(self, agent_id, agent_role, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: + async def reset_agent(self, agent_id, agent_role:AgentRole, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]: game_state = self._create_state_from_view(agent_initial_view) goal_state = self._create_goal_state_from_view(agent_win_condition_view) return game_state, goal_state From 2106608c8c945c374608208ee3ca045dd605d5a9 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 12:46:34 +0100 Subject: [PATCH 084/112] added hash to agentrole --- netsecgame/game_components.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index ac97b0a1..3db17e21 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -877,6 +877,15 @@ def __eq__(self, other: object) -> bool: elif isinstance(other, str): return self.value.lower() == other.lower().replace("agentrole.", "") return False + + def __hash__(self) -> int: + """ + Compute the hash of the AgentRole. + + Returns: + int: The hash value. + """ + return hash(self.value) @classmethod def from_string(cls, name: str) -> AgentRole: From 99c954d9384094ddf1955f6859c1a9ff214a37db Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 12:51:27 +0100 Subject: [PATCH 085/112] Added tests for AgentRole --- tests/components/test_enums.py | 53 ++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/components/test_enums.py b/tests/components/test_enums.py index 681e297a..688057ed 100644 --- a/tests/components/test_enums.py +++ b/tests/components/test_enums.py @@ -1,5 +1,6 @@ import pytest -from netsecgame.game_components import GameStatus, AgentStatus, Observation, GameState, ProtocolConfig +from netsecgame.game_components import GameStatus, AgentStatus, Observation, GameState, ProtocolConfig, AgentRole +import json class TestGameStatus: def test_from_string_valid(self): @@ -69,4 +70,52 @@ def test_constants(self): """Test protocol constants""" conf = ProtocolConfig() assert conf.END_OF_MESSAGE == b"EOF" - assert conf.BUFFER_SIZE == 8192 + +class TestAgentRole: + def test_values(self): + """Test enum values""" + assert AgentRole.Attacker.value == "Attacker" + assert AgentRole.Defender.value == "Defender" + assert AgentRole.Benign.value == "Benign" + + def test_to_string(self): + """Test to_string method""" + assert AgentRole.Attacker.to_string() == "Attacker" + assert AgentRole.Defender.to_string() == "Defender" + + def test_from_string(self): + """Test from_string method""" + assert AgentRole.from_string("Attacker") == AgentRole.Attacker + assert AgentRole.from_string("attacker") == AgentRole.Attacker + assert AgentRole.from_string("AgentRole.Attacker") == AgentRole.Attacker + + with pytest.raises(ValueError): + AgentRole.from_string("InvalidRole") + + def test_equality(self): + """Test equality comparison""" + # Compare with Enum + assert AgentRole.Attacker == AgentRole.Attacker + assert AgentRole.Attacker != AgentRole.Defender + + # Compare with String + assert AgentRole.Attacker == "Attacker" + assert AgentRole.Attacker == "attacker" # Case insensitive + assert AgentRole.Attacker != "Defender" + + def test_hashability(self): + """Test usage as dictionary key""" + d = {AgentRole.Attacker: 1, AgentRole.Defender: 2} + assert d[AgentRole.Attacker] == 1 + assert d["Attacker"] == 1 # Matches string equivalent + assert d[AgentRole.Defender] == 2 + + def test_json_serialization(self): + """Test native JSON serialization""" + data = {"role": AgentRole.Attacker} + json_str = json.dumps(data) + assert json_str == '{"role": "Attacker"}' + + # Round trip + decoded = json.loads(json_str) + assert decoded["role"] == "Attacker" From 9df4b4f92affe7c64b611c76b14d760a41fa29c0 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 12:52:11 +0100 Subject: [PATCH 086/112] Expose AgentRole in root --- netsecgame/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netsecgame/__init__.py b/netsecgame/__init__.py index 45793a3e..3f4df292 100644 --- a/netsecgame/__init__.py +++ b/netsecgame/__init__.py @@ -8,6 +8,7 @@ Action, ActionType, AgentInfo, + AgentRole, Data, GameState, GameStatus, @@ -44,6 +45,7 @@ "Action", "ActionType", "AgentInfo", + "AgentRole", "Data", "GameState", "GameStatus", From f32d6c36d64aff9c363cd2e0ea0a70d6a69c6eba Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 13:17:33 +0100 Subject: [PATCH 087/112] Split run_game into smaller methd to imporve testability and readability --- netsecgame/game/coordinator.py | 82 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index fa0fa4b8..6a728bd5 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -2,6 +2,7 @@ import json import asyncio from datetime import datetime +from typing import Optional import signal import os from aiohttp import ClientSession @@ -329,42 +330,67 @@ async def start_tasks(self): await asyncio.gather(*self._tasks, return_exceptions=True) # Wait for all tasks to finish self.logger.info("All tasks shut down.") + def _parse_action_message(self, agent_addr: tuple, message: str) -> Optional[Action]: + """ + Parses a JSON message from an agent into an Action object. + + Args: + agent_addr (tuple): The address of the agent sending the message (used for logging context). + message (str): The raw JSON string message received from the agent. + + Returns: + Optional[Action]: The parsed Action object if successful, None otherwise. + """ + try: + action = Action.from_json(message) + return action + except Exception as e: + self.logger.error(f"Error when converting msg from {agent_addr} to Action using Action.from_json():{e}, {message}") + return None + + def _dispatch_action(self, agent_addr: tuple, action: Action) -> None: + """ + Dispatches an Action to the appropriate processing method based on its type. + + Args: + agent_addr (tuple): The address of the agent performing the action. + action (Action): The Action object to be processed. + """ + match action.type: + case ActionType.JoinGame: + self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.JoinGame by {agent_addr}") + self._spawn_task(self._process_join_game_action, agent_addr, action) + case ActionType.QuitGame: + self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.QuitGame by {agent_addr}") + self._spawn_task(self._process_quit_game_action, agent_addr) + case ActionType.ResetGame: + self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.ResetGame by {agent_addr}") + self._spawn_task(self._process_reset_game_action, agent_addr, action) + case ActionType.ExfiltrateData | ActionType.FindData | ActionType.ScanNetwork | ActionType.FindServices | ActionType.ExploitService: + self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") + self._spawn_task(self._process_game_action, agent_addr, action) + case ActionType.BlockIP: + self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") + self._spawn_task(self._process_game_action, agent_addr, action) + case _: + self.logger.warning(f"About agent {agent_addr}. Unsupported action type: {action}!") + async def run_game(self): """ - Task responsible for reading messages from the agent queue and processing them based on the ActionType. + Main game loop task. + + Responsible for reading messages from the agent queue, parsing them using `_parse_action_message`, + and dispatching them to the appropriate handler using `_dispatch_action`. """ while not self.shutdown_flag.is_set(): # Read message from the queue agent_addr, message = await self._agent_action_queue.get() if message is not None: self.logger.info(f"Coordinator received from agent {agent_addr}: {message}.") - - try: # Convert message to Action - action = Action.from_json(message) - self.logger.debug(f"\tConverted to: {action}.") - match action.type: # process action based on its type - case ActionType.JoinGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.JoinGame by {agent_addr}") - self.logger.debug(f"{action.type}, {action.type.value}, {action.type == ActionType.JoinGame}") - self._spawn_task(self._process_join_game_action, agent_addr, action) - case ActionType.QuitGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.QuitGame by {agent_addr}") - self._spawn_task(self._process_quit_game_action, agent_addr) - case ActionType.ResetGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.ResetGame by {agent_addr}") - self._spawn_task(self._process_reset_game_action, agent_addr, action) - case ActionType.ExfiltrateData | ActionType.FindData | ActionType.ScanNetwork | ActionType.FindServices | ActionType.ExploitService: - self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") - self._spawn_task(self._process_game_action, agent_addr, action) - case ActionType.BlockIP: - self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") - self._spawn_task(self._process_game_action, agent_addr, action) - case _: - self.logger.warning(f"About agent {agent_addr}. Unsupported action type: {action}!") - except Exception as e: - self.logger.error( - f"Error when converting msg to Action using Action.from_json():{e}, {message}" - ) + + action = self._parse_action_message(agent_addr, message) + if action: + self._dispatch_action(agent_addr, action) self.logger.info("\tAction processing task stopped.") async def _process_join_game_action(self, agent_addr: tuple, action: Action)->None: From cba6a286fcf4857c83cbd640de2ce80ef22bfa64 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 13:24:01 +0100 Subject: [PATCH 088/112] New tests for refactored methods (_parse_action, _dispatch_action, run_game) --- tests/game/test_coordinator_core.py | 86 ++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/tests/game/test_coordinator_core.py b/tests/game/test_coordinator_core.py index ced6a1c0..70e6d0fd 100644 --- a/tests/game/test_coordinator_core.py +++ b/tests/game/test_coordinator_core.py @@ -377,4 +377,88 @@ async def test_process_game_action_ongoing_episode(initialized_coordinator, empt assert '"status": "' + str(GameStatus.OK) + '"' in msg_json assert '"reward": 0' in msg_json assert '"end": false' in msg_json - assert '"info": {}' in msg_json \ No newline at end of file + assert '"info": {}' in msg_json + +# ----------------------- +# New tests for refactored methods (_parse_action, _dispatch_action, run_game) +# ----------------------- +class TestCoordinatorRefactoredMethods: + @pytest.fixture + def mock_coordinator_core(self): + # Create a mock coordinator slightly different from integration fixtures to purely test logic + coord = MagicMock(spec=GameCoordinator) + coord.logger = MagicMock() + coord._agent_action_queue = AsyncMock() + coord.shutdown_flag = MagicMock() + # Side effect to stop loop after one iteration + coord.shutdown_flag.is_set.side_effect = [False, True] + + # Bind refactored methods + coord._parse_action_message = GameCoordinator._parse_action_message.__get__(coord) + coord._dispatch_action = GameCoordinator._dispatch_action.__get__(coord) + coord.run_game = GameCoordinator.run_game.__get__(coord) + + # Set __name__ for the mocked handlers so assert .__name__ works + coord._process_join_game_action.__name__ = "_process_join_game_action" + coord._process_quit_game_action.__name__ = "_process_quit_game_action" + coord._process_reset_game_action.__name__ = "_process_reset_game_action" + coord._process_game_action.__name__ = "_process_game_action" + + return coord + + def test_parse_action_message_valid(self, mock_coordinator_core): + """New test for refactored method: _parse_action_message with valid input.""" + from netsecgame.game_components import AgentRole + valid_json = '{"action_type": "ActionType.JoinGame", "parameters": {"agent_info": {"name": "TestAgent", "role": "Attacker"}}}' + agent_addr = ("127.0.0.1", 12345) + + action = mock_coordinator_core._parse_action_message(agent_addr, valid_json) + + assert action is not None + assert action.type == ActionType.JoinGame + assert action.parameters["agent_info"].role == AgentRole.Attacker + + def test_parse_action_message_invalid(self, mock_coordinator_core): + """New test for refactored method: _parse_action_message with invalid input.""" + invalid_json = '{"invalid": "json"}' + agent_addr = ("127.0.0.1", 12345) + + action = mock_coordinator_core._parse_action_message(agent_addr, invalid_json) + + assert action is None + mock_coordinator_core.logger.error.assert_called() + # Verify agent address is in the error log + args, _ = mock_coordinator_core.logger.error.call_args + assert str(agent_addr) in args[0] + + def test_dispatch_action(self, mock_coordinator_core): + """New test for refactored method: _dispatch_action routing.""" + action = Action(ActionType.ScanNetwork, parameters={}) + agent_addr = ("127.0.0.1", 12345) + + mock_coordinator_core._dispatch_action(agent_addr, action) + + mock_coordinator_core._spawn_task.assert_called_once() + args = mock_coordinator_core._spawn_task.call_args[0] + # Should route to _process_game_action for ScanNetwork + assert args[0].__name__ == "_process_game_action" + + @pytest.mark.asyncio + async def test_run_game_flow(self, mock_coordinator_core): + """New test for refactored method: run_game flow (parse -> dispatch).""" + agent_addr = ("127.0.0.1", 12345) + valid_json = '{"action_type": "ActionType.ScanNetwork", "parameters": {}}' + + # Setup queue + mock_coordinator_core._agent_action_queue.get.return_value = (agent_addr, valid_json) + + with patch.object(mock_coordinator_core, '_parse_action_message') as mock_parse, \ + patch.object(mock_coordinator_core, '_dispatch_action') as mock_dispatch: + + mock_action = Action(ActionType.ScanNetwork, {}) + mock_parse.return_value = mock_action + + await mock_coordinator_core.run_game() + + mock_parse.assert_called_once_with(agent_addr, valid_json) + mock_dispatch.assert_called_once_with(agent_addr, mock_action) \ No newline at end of file From 6012c11f81e9e571de0a78d7798c53e5be3f0066 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 13:26:09 +0100 Subject: [PATCH 089/112] better logging --- netsecgame/game/agent_context.py | 83 ++++++++++++++++++++++++++++++++ netsecgame/game/coordinator.py | 17 +++---- 2 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 netsecgame/game/agent_context.py diff --git a/netsecgame/game/agent_context.py b/netsecgame/game/agent_context.py new file mode 100644 index 00000000..956fceeb --- /dev/null +++ b/netsecgame/game/agent_context.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass, field +import asyncio +from typing import Optional, Dict, Any + +from netsecgame.game_components import GameState, AgentStatus, Action, Observation +from netsecgame.utils.trajectory_recorder import TrajectoryRecorder + +@dataclass +class AgentContext: + """ + Encapsulates all state and information related to a single connected agent in the game. + """ + name: str + role: str + address: tuple + + # State + current_state: GameState + goal_state: GameState + starting_position: dict + + # Status + status: AgentStatus = AgentStatus.Playing + episode_end: bool = False + + # Reset flags + reset_request: bool = False + topology_reset_request: bool = False + + # Metrics + steps: int = 0 + rewards: float = 0.0 + false_positives: int = 0 + + # Last interaction + last_action: Optional[Action] = None + current_observation: Optional[Observation] = None + + # Generic extensibility for World-specific data + custom_data: Dict[str, Any] = field(default_factory=dict) + + # Trajectory + recorder: Optional[TrajectoryRecorder] = None + + def __post_init__(self): + """ + Initialize the TrajectoryRecorder after the object is created. + """ + self.recorder = TrajectoryRecorder(self.name, self.role) + # Initialize recorder with current state if needed, or caller does it. + # self.recorder.add_initial_state(self.current_state) + + def reset_for_new_episode(self, new_state: GameState, new_goal_state: GameState, timeout_role: bool = False): + """ + Resets the agent's ephemeral state for a new episode. + """ + self.current_state = new_state + self.goal_state = new_goal_state + self.episode_end = False + self.reset_request = False + self.topology_reset_request = False + self.steps = 0 + self.rewards = 0.0 + self.false_positives = 0 + self.last_action = None + + # Reset Status + if timeout_role: + self.status = AgentStatus.PlayingWithTimeout + else: + self.status = AgentStatus.Playing + + # Reset Trajectory + if self.recorder: + self.recorder.reset() + self.recorder.add_initial_state(new_state) + + def record_step(self, action: Action, reward: float, next_state: GameState, end_reason: Optional[str] = None): + """ + Delegates step recording to the TrajectoryRecorder. + """ + if self.recorder: + self.recorder.add_step(action, reward, next_state, end_reason) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 6a728bd5..fa10b825 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -358,22 +358,19 @@ def _dispatch_action(self, agent_addr: tuple, action: Action) -> None: """ match action.type: case ActionType.JoinGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.JoinGame by {agent_addr}") + self.logger.debug(f"[{agent_addr}] Start processing of ActionType.JoinGame") self._spawn_task(self._process_join_game_action, agent_addr, action) case ActionType.QuitGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.QuitGame by {agent_addr}") + self.logger.debug(f"[{agent_addr}] Start processing of ActionType.QuitGame") self._spawn_task(self._process_quit_game_action, agent_addr) case ActionType.ResetGame: - self.logger.debug(f"About agent {agent_addr}. Start processing of ActionType.ResetGame by {agent_addr}") + self.logger.debug(f"[{agent_addr}] Start processing of ActionType.ResetGame") self._spawn_task(self._process_reset_game_action, agent_addr, action) - case ActionType.ExfiltrateData | ActionType.FindData | ActionType.ScanNetwork | ActionType.FindServices | ActionType.ExploitService: - self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") - self._spawn_task(self._process_game_action, agent_addr, action) - case ActionType.BlockIP: - self.logger.debug(f"About agent {agent_addr}. Start processing of {action.type} by {agent_addr}") + case ActionType.ExfiltrateData | ActionType.FindData | ActionType.ScanNetwork | ActionType.FindServices | ActionType.ExploitService | ActionType.BlockIP: + self.logger.debug(f"[{agent_addr}] Start processing of {action.type}") self._spawn_task(self._process_game_action, agent_addr, action) case _: - self.logger.warning(f"About agent {agent_addr}. Unsupported action type: {action}!") + self.logger.warning(f"[{agent_addr}] Unsupported action type: {action}!") async def run_game(self): """ @@ -387,7 +384,7 @@ async def run_game(self): agent_addr, message = await self._agent_action_queue.get() if message is not None: self.logger.info(f"Coordinator received from agent {agent_addr}: {message}.") - + action = self._parse_action_message(agent_addr, message) if action: self._dispatch_action(agent_addr, action) From c263a46deac49f94b7955dd3308645083f466c37 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 13:26:44 +0100 Subject: [PATCH 090/112] Remove unused file --- netsecgame/game/agent_context.py | 83 -------------------------------- 1 file changed, 83 deletions(-) delete mode 100644 netsecgame/game/agent_context.py diff --git a/netsecgame/game/agent_context.py b/netsecgame/game/agent_context.py deleted file mode 100644 index 956fceeb..00000000 --- a/netsecgame/game/agent_context.py +++ /dev/null @@ -1,83 +0,0 @@ -from dataclasses import dataclass, field -import asyncio -from typing import Optional, Dict, Any - -from netsecgame.game_components import GameState, AgentStatus, Action, Observation -from netsecgame.utils.trajectory_recorder import TrajectoryRecorder - -@dataclass -class AgentContext: - """ - Encapsulates all state and information related to a single connected agent in the game. - """ - name: str - role: str - address: tuple - - # State - current_state: GameState - goal_state: GameState - starting_position: dict - - # Status - status: AgentStatus = AgentStatus.Playing - episode_end: bool = False - - # Reset flags - reset_request: bool = False - topology_reset_request: bool = False - - # Metrics - steps: int = 0 - rewards: float = 0.0 - false_positives: int = 0 - - # Last interaction - last_action: Optional[Action] = None - current_observation: Optional[Observation] = None - - # Generic extensibility for World-specific data - custom_data: Dict[str, Any] = field(default_factory=dict) - - # Trajectory - recorder: Optional[TrajectoryRecorder] = None - - def __post_init__(self): - """ - Initialize the TrajectoryRecorder after the object is created. - """ - self.recorder = TrajectoryRecorder(self.name, self.role) - # Initialize recorder with current state if needed, or caller does it. - # self.recorder.add_initial_state(self.current_state) - - def reset_for_new_episode(self, new_state: GameState, new_goal_state: GameState, timeout_role: bool = False): - """ - Resets the agent's ephemeral state for a new episode. - """ - self.current_state = new_state - self.goal_state = new_goal_state - self.episode_end = False - self.reset_request = False - self.topology_reset_request = False - self.steps = 0 - self.rewards = 0.0 - self.false_positives = 0 - self.last_action = None - - # Reset Status - if timeout_role: - self.status = AgentStatus.PlayingWithTimeout - else: - self.status = AgentStatus.Playing - - # Reset Trajectory - if self.recorder: - self.recorder.reset() - self.recorder.add_initial_state(new_state) - - def record_step(self, action: Action, reward: float, next_state: GameState, end_reason: Optional[str] = None): - """ - Delegates step recording to the TrajectoryRecorder. - """ - if self.recorder: - self.recorder.add_step(action, reward, next_state, end_reason) From 92b97227ac0d660b9b5a967d7ceae00a3fb914c8 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 13:43:53 +0100 Subject: [PATCH 091/112] update doctrings, separete method for dict to msg conversion --- netsecgame/game/coordinator.py | 79 +++++++++++++++++++++-------- tests/game/test_coordinator_core.py | 5 +- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index fa10b825..9d48bf37 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -15,6 +15,19 @@ from cyst.api.environment.environment import Environment +def convert_msg_dict_to_json(msg_dict: dict) -> str: + """ + Helper function to create text-base messge from a dictionary. Used in the Agent-Game communication. + """ + try: + # Convert message into string representation + output_message = json.dumps(msg_dict) + except Exception as e: + # Let the caller handle logging if needed, or re-raise with context + raise TypeError(f"Error when converting msg to JSON:{e}") from e + return output_message + + class GameCoordinator: """ Class for creation, and management of agent interactions in AI Dojo. @@ -110,7 +123,17 @@ def __init__(self, game_host: str, game_port: int, service_host:str, service_por self._agent_trajectories = {} def _spawn_task(self, coroutine, *args, **kwargs)->asyncio.Task: - "Helper function to make sure all tasks are registered for proper termination" + """ + Helper function to make sure all tasks are registered for proper termination. + + Args: + coroutine: The coroutine function to schedule. + *args: Positional arguments to pass to the coroutine. + **kwargs: Keyword arguments to pass to the coroutine. + + Returns: + asyncio.Task: The created task object. + """ task = asyncio.create_task(coroutine(*args, **kwargs)) self._tasks.add(task) def remove_task(t): @@ -118,31 +141,23 @@ def remove_task(t): task.add_done_callback(remove_task) # Remove task when done return task - async def shutdown_signal_handler(self): - """Handle shutdown signals.""" + async def shutdown_signal_handler(self)->None: + """ + Logs the signal reception and sets the shutdown flag to initiate graceful termination. + """ self.logger.info("Shutdown signal received. Setting shutdown flag.") self.shutdown_flag.set() async def create_agent_queue(self, agent_addr:tuple)->None: """ Creates a queue for the given agent address if it doesn't already exist. + + Args: + agent_addr (tuple): The agent address to create a queue for. """ if agent_addr not in self._agent_response_queues: self._agent_response_queues[agent_addr] = asyncio.Queue() self.logger.info(f"Created queue for agent {agent_addr}. {len(self._agent_response_queues)} queues in total.") - - def convert_msg_dict_to_json(self, msg_dict:dict)->str: - """ - Helper function to create text-base messge from a dictionary. Used in the Agent-Game communication. - """ - try: - # Convert message into string representation - output_message = json.dumps(msg_dict) - except Exception as e: - self.logger.error(f"Error when converting msg to JSON:{e}") - raise e - # Send to anwer_queue - return output_message def run(self)->None: """ @@ -435,7 +450,7 @@ async def _process_join_game_action(self, agent_addr: tuple, action: Action)->No if hasattr(self, "_registration_info"): for key, value in self._registration_info.items(): output_message_dict["message"][key] = value - await self._agent_response_queues[agent_addr].put(self.convert_msg_dict_to_json(output_message_dict)) + await self._agent_response_queues[agent_addr].put(convert_msg_dict_to_json(output_message_dict)) else: self.logger.info( f"\tError in registration, unknown agent role: {agent_role}!" @@ -445,7 +460,7 @@ async def _process_join_game_action(self, agent_addr: tuple, action: Action)->No "status": str(GameStatus.BAD_REQUEST), "message": f"Incorrect agent_role {agent_role}", } - response_msg_json = self.convert_msg_dict_to_json(output_message_dict) + response_msg_json = convert_msg_dict_to_json(output_message_dict) await self._agent_response_queues[agent_addr].put(response_msg_json) else: self.logger.info("\tError in registration, agent already exists!") @@ -454,7 +469,7 @@ async def _process_join_game_action(self, agent_addr: tuple, action: Action)->No "status": str(GameStatus.BAD_REQUEST), "message": "Agent already exists.", } - response_msg_json = self.convert_msg_dict_to_json(output_message_dict) + response_msg_json = convert_msg_dict_to_json(output_message_dict) await self._agent_response_queues[agent_addr].put(response_msg_json) except asyncio.CancelledError: self.logger.debug(f"Proccessing JoinAction of agent {agent_addr} interrupted") @@ -521,7 +536,7 @@ async def _process_reset_game_action(self, agent_addr: tuple, reset_action:Actio if "request_trajectory" in reset_action.parameters and reset_action.parameters["request_trajectory"]: output_message_dict["message"]["last_trajectory"] = self._agent_trajectories[agent_addr] self._agent_trajectories[agent_addr] = self._reset_trajectory(agent_addr) - response_msg_json = self.convert_msg_dict_to_json(output_message_dict) + response_msg_json = convert_msg_dict_to_json(output_message_dict) await self._agent_response_queues[agent_addr].put(response_msg_json) async def _process_game_action(self, agent_addr: tuple, action:Action)->None: @@ -592,7 +607,7 @@ async def _process_game_action(self, agent_addr: tuple, action:Action)->None: "observation": observation_as_dict(new_observation), "status": str(GameStatus.OK), } - response_msg_json = self.convert_msg_dict_to_json(output_message_dict) + response_msg_json = convert_msg_dict_to_json(output_message_dict) await self._agent_response_queues[agent_addr].put(response_msg_json) async def _assign_rewards_episode_end(self): @@ -764,14 +779,23 @@ async def _remove_agent_from_game(self, agent_addr): return agent_info async def step(self, agent_id:tuple, agent_state:GameState, action:Action): + """ + Domain specific method of the environment. Creates the initial state of the agent. + Must be implemented by the domain specific environment. + """ raise NotImplementedError async def reset(self)->bool: + """ + Domain specific method of the environment. Creates the initial state of the agent. + Must be implemented by the domain specific environment. + """ raise NotImplementedError def _initialize(self): """ Initialize the game state and other necessary components. This is called at the start of the game after the configuration is loaded. + Must be implemented by the domain specific environment. """ raise NotImplementedError @@ -829,6 +853,8 @@ def is_timeout(self, agent:tuple)->bool: def add_false_positive(self, agent:tuple)->None: """ Method for adding false positive to the agent. + Args: + agent (tuple): The agent to add false positive to. """ self.logger.debug(f"Adding false positive to {agent}") if agent in self._agent_false_positives: @@ -840,6 +866,10 @@ def add_false_positive(self, agent:tuple)->None: def _update_agent_status(self, agent:tuple)->AgentStatus: """ Update the status of an agent based on reaching the goal, timeout or detection. + Args: + agent (tuple): The agent to update the status of. + Returns: + AgentStatus: The new status of the agent. """ # read current status of the agent next_status = self._agent_status[agent] @@ -858,6 +888,13 @@ def _update_agent_status(self, agent:tuple)->AgentStatus: return next_status def _update_agent_episode_end(self, agent:tuple)->bool: + """ + Update the episode end status of an agent. + Args: + agent (tuple): The agent to update the episode end status of. + Returns: + bool: True if the episode has ended, False otherwise. + """ episode_end = False if self._agent_status[agent] in [AgentStatus.Success, AgentStatus.Fail, AgentStatus.TimeoutReached]: # agent reached goal, timeout or was detected diff --git a/tests/game/test_coordinator_core.py b/tests/game/test_coordinator_core.py index 70e6d0fd..367c011a 100644 --- a/tests/game/test_coordinator_core.py +++ b/tests/game/test_coordinator_core.py @@ -7,6 +7,7 @@ from netsecgame.game.coordinator import GameCoordinator from netsecgame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus +from netsecgame.game.coordinator import convert_msg_dict_to_json # ----------------------- # Fixtures @@ -106,7 +107,7 @@ async def test_load_initialization_objects_loads_config(gc_with_test_config): def test_convert_msg_dict_to_json_success(gc_with_test_config): """Test that convert_msg_dict_to_json correctly serializes a dictionary.""" msg = {"foo": "bar"} - json_str = gc_with_test_config.convert_msg_dict_to_json(msg) + json_str = convert_msg_dict_to_json(msg) assert json_str == '{"foo": "bar"}' @@ -116,7 +117,7 @@ class Unserializable: pass with pytest.raises(TypeError): - gc_with_test_config.convert_msg_dict_to_json({"bad": Unserializable()}) + convert_msg_dict_to_json({"bad": Unserializable()}) @pytest.mark.asyncio From 88faadacbd373beafc36d8cbb918e35d4713bc3d Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 14:36:06 +0100 Subject: [PATCH 092/112] Add default values --- netsecgame/game/config_parser.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/netsecgame/game/config_parser.py b/netsecgame/game/config_parser.py index 5de253b5..db67c74e 100644 --- a/netsecgame/game/config_parser.py +++ b/netsecgame/game/config_parser.py @@ -328,26 +328,26 @@ def get_rewards(self, reward_names:list, default_value=0)->dict: rewards[name] = default_value return rewards - def get_use_dynamic_addresses(self)->bool: + def get_use_dynamic_addresses(self, default_value: bool = False)->bool: """ Reads if the IP and Network addresses should be dynamically changed. """ try: use_dynamic_addresses = self.config['env']['use_dynamic_addresses'] except KeyError: - use_dynamic_addresses = False + use_dynamic_addresses = default_value return bool(use_dynamic_addresses) - def get_store_trajectories(self): + def get_store_trajectories(self, default_value: bool = False): """ Read if the replay buffer should be stored in file """ try: - store_rb = self.config['env']['save_trajectories'] + store_trajectories = self.config['env']['save_trajectories'] except KeyError: # Option is not in the configuration - default to FALSE - store_rb = False - return store_rb + store_trajectories = default_value + return store_trajectories def get_scenario(self): """ @@ -382,7 +382,7 @@ def get_seed(self, whom): seed = randint(0,100) return seed - def get_randomize_goal_every_episode(self) -> bool: + def get_randomize_goal_every_episode(self, default_value: bool = False) -> bool: """ Get if the randomization should be done only once or at the beginning of every episode """ @@ -390,32 +390,32 @@ def get_randomize_goal_every_episode(self) -> bool: randomize_goal_every_episode = self.config["coordinator"]["agents"]["attackers"]["goal"]["is_any_part_of_goal_random"] except KeyError: # Option is not in the configuration - default to FALSE - randomize_goal_every_episode = False + randomize_goal_every_episode = default_value return randomize_goal_every_episode - def get_use_firewall(self)->bool: + def get_use_firewall(self, default_value: bool = False)->bool: """ Retrieves if the firewall functionality is allowed for netsecgame. Default: False """ try: - use_firewall = self.config['env']['use_firewall'] + use_firewall = self.config['env']['use_firewall'] except KeyError: - use_firewall = False + use_firewall = default_value return use_firewall - def get_use_global_defender(self)->bool: + def get_use_global_defender(self, default_value: bool = False)->bool: try: use_global_defender = self.config['env']['use_global_defender'] except KeyError: - use_global_defender = False + use_global_defender = default_value return use_global_defender - def get_required_num_players(self)->int: + def get_required_num_players(self, default_value: int = 1)->int: try: required_players = int(self.config['env']['required_players']) except KeyError: - required_players = 1 + required_players = default_value except ValueError: - required_players = 1 + required_players = default_value return required_players \ No newline at end of file From 72d5a90d56fd966a43fa69a718eedd5a9c56bb64 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 14:37:27 +0100 Subject: [PATCH 093/112] split Configuration manager --- netsecgame/game/configuration_manager.py | 158 +++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 netsecgame/game/configuration_manager.py diff --git a/netsecgame/game/configuration_manager.py b/netsecgame/game/configuration_manager.py new file mode 100644 index 00000000..d8e1a65f --- /dev/null +++ b/netsecgame/game/configuration_manager.py @@ -0,0 +1,158 @@ +import logging +from typing import Optional, Dict, Any, List +import asyncio +from aiohttp import ClientSession + +from netsecgame.game.config_parser import ConfigParser +from netsecgame.utils.utils import get_str_hash +from cyst.api.environment.environment import Environment +from netsecgame.game_components import AgentRole + +class ConfigurationManager: + """ + Manages the loading and access of game configuration. + + Handles fetching configuration from efficient sources (local file or remote service) + and provides structured access to configuration data. + """ + + def __init__(self, task_config_file: Optional[str] = None, service_host: Optional[str] = None, service_port: Optional[int] = None): + self.logger = logging.getLogger("GameCoordinator-ConfigurationManager") + self._task_config_file = task_config_file + self._service_host = service_host + self._service_port = service_port + + self._parser: Optional[ConfigParser] = None + self._cyst_objects = None + self._config_file_hash: Optional[str] = None + + # Cache for parsed values + self._starting_positions: Dict[str, Any] = {} + self._win_conditions: Dict[str, Any] = {} + self._goal_descriptions: Dict[str, str] = {} + self._max_steps: Dict[str, Optional[int]] = {} + + async def load(self) -> None: + """ + Determines the source and loads the configuration. + Prioritizes remote service if configured, otherwise falls back to local file. + """ + if self._service_host and self._service_port: + self.logger.info(f"Fetching task configuration from {self._service_host}:{self._service_port}") + await self._fetch_remote_configuration() + elif self._task_config_file: + self.logger.info(f"Loading task configuration from file: {self._task_config_file}") + self._load_local_configuration() + else: + raise ValueError("Task configuration source not specified (neither file nor service)") + + async def _fetch_remote_configuration(self) -> None: + """Fetches initialization objects from the remote service.""" + url = f"http://{self._service_host}:{self._service_port}/cyst_init_objects" + async with ClientSession() as session: + try: + async with session.get(url) as response: + if response.status == 200: + config_data = await response.json() + self.logger.debug(f"Received config data: {config_data}") + + # Initialize CYST environment + env = Environment.create() + self._config_file_hash = get_str_hash(config_data) + self._cyst_objects = env.configuration.general.load_configuration(config_data) + self.logger.debug(f"Initialization objects received: {self._cyst_objects}") + + # Initialize parser with the fetched dict (assuming it contains task_configuration or similar structure) + # Note: The original coordinator code for remote fetch commented out creating ConfigParser: + # #self.task_config = ConfigParser(config_dict=response["task_configuration"]) + # usage of self.task_config in original code fell back to loading from file even if remote fetch happened? + # "Temporary fix" comment in original code suggests fallback. + # For this implementation, we should try to use the fetched config if possible. + # If the API returns the same structure as the YAML file, we can pass it to ConfigParser(config_dict=...) + # If not, we might need to rely on the file as the original code did for the parser part. + + # Let's assume for now we try to use the dictionary if available, otherwise fallback logic might be needed + # derived from how the response is structured. + # Looking at original code: response seems to be the full config. + self._parser = ConfigParser(config_dict=config_data) + + else: + self.logger.error(f"Failed to fetch initialization objects. Status: {response.status}") + raise RuntimeError(f"Remote configuration fetch failed with status {response.status}") + except Exception as e: + self.logger.error(f"Error fetching initialization objects: {e}") + # Fallback to local file if remote fails? The original code did: + # self.task_config = ConfigParser(self._task_config_file) + # We can implement similar fallback behavior here if desired, or just raise. + if self._task_config_file: + self.logger.warning("Falling back to local configuration file.") + self._load_local_configuration() + else: + raise e + + def _load_local_configuration(self) -> None: + """Loads configuration from the local file.""" + self._parser = ConfigParser(task_config_file=self._task_config_file) + self._cyst_objects = self._parser.get_scenario() + # Original code does str(self._cyst_objects) for hash + self._config_file_hash = get_str_hash(str(self._cyst_objects)) + + # ------------------------------------------------------------------------- + # Accessors + # ------------------------------------------------------------------------- + + def get_cyst_objects(self): + return self._cyst_objects + + def get_config_hash(self) -> Optional[str]: + return self._config_file_hash + + def get_starting_position(self, role: str) -> dict: + """Returns the starting position configuration for a specific role.""" + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_start_position(agent_role=role) + + def get_win_conditions(self, role: str) -> dict: + """Returns the win conditions for a specific role.""" + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_win_conditions(agent_role=role) + + def get_goal_description(self, role: str) -> str: + """Returns the goal description for a specific role.""" + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_goal_description(agent_role=role) + + def get_max_steps(self, role: str) -> Optional[int]: + """Returns the max steps for a specific role.""" + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_max_steps(role) + + def get_rewards(self, reward_names: List[str] = ["step", "success", "fail", "false_positive"], default_value: int = 0) -> dict: + """Returns the rewards configuration.""" + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_rewards(reward_names, default_value) + + def get_use_dynamic_ips(self, default_value: bool = False) -> bool: + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_use_dynamic_addresses(default_value) + + def get_use_global_defender(self, default_value: bool = False) -> bool: + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_use_global_defender(default_value) + + def get_required_num_players(self, default_value: int = 1) -> int: + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_required_num_players(default_value) + + def get_use_firewall(self, default_value: bool = False) -> bool: + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_use_firewall(default_value) From aec50d82286240df7c31e2a5e3ef03b1789aeb5c Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 14:38:44 +0100 Subject: [PATCH 094/112] Use AgentRole --- netsecgame/game/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 9d48bf37..2fcf09e5 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -418,7 +418,7 @@ async def _process_join_game_action(self, agent_addr: tuple, action: Action)->No if agent_addr not in self.agents: agent_name = action.parameters["agent_info"].name agent_role = action.parameters["agent_info"].role - if agent_role in self.ALLOWED_ROLES: + if agent_role in AgentRole: # add agent to the world new_agent_game_state, new_agent_goal_state = await self.register_agent(agent_addr, agent_role, self._starting_positions_per_role[agent_role], self._win_conditions_per_role[agent_role]) if new_agent_game_state: # successful registration From 3092eb43c0e09b8d5dd2f55e8ccd94721c43488e Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:03:39 +0100 Subject: [PATCH 095/112] Move functionality to Configuration manager --- netsecgame/game/configuration_manager.py | 42 +++++++++ netsecgame/game/coordinator.py | 113 +++++------------------ netsecgame/game/worlds/NetSecGame.py | 4 +- tests/game/test_coordinator_core.py | 105 ++++++++++++--------- 4 files changed, 127 insertions(+), 137 deletions(-) diff --git a/netsecgame/game/configuration_manager.py b/netsecgame/game/configuration_manager.py index d8e1a65f..9b68aaf1 100644 --- a/netsecgame/game/configuration_manager.py +++ b/netsecgame/game/configuration_manager.py @@ -156,3 +156,45 @@ def get_use_firewall(self, default_value: bool = False) -> bool: if not self._parser: raise RuntimeError("Configuration not loaded.") return self._parser.get_use_firewall(default_value) + + def get_all_starting_positions(self) -> Dict[str, Any]: + """Returns starting positions for all roles.""" + starting_positions = {} + for agent_role in AgentRole: + try: + starting_positions[agent_role] = self.get_starting_position(role=agent_role) + self.logger.info(f"Starting position for role '{agent_role}': {starting_positions[agent_role]}") + except KeyError: + starting_positions[agent_role] = {} + return starting_positions + + def get_all_win_conditions(self) -> Dict[str, Any]: + """Returns win conditions for all roles.""" + win_conditions = {} + for agent_role in AgentRole: + try: + win_conditions[agent_role] = self.get_win_conditions(role=agent_role) + except KeyError: + win_conditions[agent_role] = {} + self.logger.info(f"Win condition for role '{agent_role}': {win_conditions[agent_role]}") + return win_conditions + + def get_all_goal_descriptions(self) -> Dict[str, str]: + """Returns goal descriptions for all roles.""" + goal_descriptions = {} + for agent_role in AgentRole: + try: + goal_descriptions[agent_role] = self.get_goal_description(role=agent_role) + except KeyError: + goal_descriptions[agent_role] = "" + self.logger.info(f"Goal description for role '{agent_role}': {goal_descriptions[agent_role]}") + return goal_descriptions + + def get_all_max_steps(self) -> Dict[str, Optional[int]]: + """Returns max steps for all roles.""" + # Using self.get_max_steps might raise RuntimeError if checks are there, + # but simpler to just call parser directly or the single accessor since we are inside the class. + # However, the single accessor has the check. + # But wait, self.get_max_steps(role) does `self._parser.get_max_steps(role)` already. + # Iterating over AgentRole is correct. + return {role: self.get_max_steps(role) for role in AgentRole} diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 2fcf09e5..10cc3aa0 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -12,6 +12,7 @@ from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl from netsecgame.game.config_parser import ConfigParser from netsecgame.game.agent_server import AgentServer +from netsecgame.game.configuration_manager import ConfigurationManager from cyst.api.environment.environment import Environment @@ -88,6 +89,10 @@ def __init__(self, game_host: str, game_port: int, service_host:str, service_por self._service_port = service_port # for reading configuration locally self._task_config_file = task_config_file + + # Configuration Manager + self.config_manager = ConfigurationManager(task_config_file, service_host, service_port) + self.logger = logging.getLogger("AIDojo-GameCoordinator") self.ALLOWED_ROLES = allowed_roles self._cyst_objects = None @@ -170,80 +175,6 @@ def run(self)->None: finally: self.logger.info(f"{__class__.__name__} has exited.") - async def _fetch_initialization_objects(self): - """Send a REST request to MAIN and fetch initialization objects of CYST simulator.""" - async with ClientSession() as session: - try: - async with session.get(f"http://{self._service_host}:{self._service_port}/cyst_init_objects") as response: - if response.status == 200: - response = await response.json() - self.logger.debug(response) - env = Environment.create() - self._CONFIG_FILE_HASH = get_str_hash(response) - self._cyst_objects = env.configuration.general.load_configuration(response) - self.logger.debug(f"Initialization objects received:{self._cyst_objects}") - #self.task_config = ConfigParser(config_dict=response["task_configuration"]) - else: - self.logger.error(f"Failed to fetch initialization objects. Status: {response.status}") - except Exception as e: - self.logger.error(f"Error fetching initialization objects: {e}") - # Temporary fix - self.task_config = ConfigParser(self._task_config_file) - - def _load_initialization_objects(self)->None: - """ - Loads task configuration from a local file. - """ - self.task_config = ConfigParser(self._task_config_file) - self._cyst_objects = self.task_config.get_scenario() - self._CONFIG_FILE_HASH = get_str_hash(str(self._cyst_objects)) - - def _get_starting_position_per_role(self)->dict: - """ - Method for finding starting position for each agent role in the game. - """ - starting_positions = {} - for agent_role in AgentRole: - try: - starting_positions[agent_role] = self.task_config.get_start_position(agent_role=agent_role) - self.logger.info(f"Starting position for role '{agent_role}': {starting_positions[agent_role]}") - except KeyError: - starting_positions[agent_role] = {} - return starting_positions - - def _get_win_condition_per_role(self)-> dict: - """ - Method for finding wininng conditions for each agent role in the game. - """ - win_conditions = {} - for agent_role in AgentRole: - try: - win_conditions[agent_role] = self.task_config.get_win_conditions(agent_role=agent_role) - except KeyError: - win_conditions[agent_role] = {} - self.logger.info(f"Win condition for role '{agent_role}': {win_conditions[agent_role]}") - return win_conditions - - def _get_goal_description_per_role(self)->dict: - """ - Method for finding goal description for each agent role in the game. - """ - goal_descriptions ={} - for agent_role in AgentRole: - try: - goal_descriptions[agent_role] = self.task_config.get_goal_description(agent_role=agent_role) - except KeyError: - goal_descriptions[agent_role] = "" - self.logger.info(f"Goal description for role '{agent_role}': {goal_descriptions[agent_role]}") - return goal_descriptions - - def _get_max_steps_per_role(self)->dict: - """ - Method for finding max amount of steps in 1 episode for each agent role in the game. - """ - max_steps = {role:self.task_config.get_max_steps(role) for role in AgentRole} - return max_steps - async def start_tcp_server(self): """ Starts TPC sever for the agent communication. @@ -293,32 +224,30 @@ async def start_tasks(self): ) - # initialize the game objects - if self._service_host: #get the task config using REST API - self.logger.info(f"Fetching task configuration from {self._service_host}:{self._service_port}") - await self._fetch_initialization_objects() - elif self._task_config_file: # load task config locally from a file - self.logger.info(f"Loading task configuration from file: {self._task_config_file}") - self._load_initialization_objects() - else: - raise ValueError("Task configuration not specified") + # Initialize configuration manager and load the configuration + await self.config_manager.load() + self._cyst_objects = self.config_manager.get_cyst_objects() + + if self.config_manager.get_config_hash(): + self._CONFIG_FILE_HASH = self.config_manager.get_config_hash() - # Read configuration - self._starting_positions_per_role = self._get_starting_position_per_role() - self._win_conditions_per_role = self._get_win_condition_per_role() - self._goal_description_per_role = self._get_goal_description_per_role() - self._steps_limit_per_role = self._get_max_steps_per_role() + # Read configuration + self._starting_positions_per_role = self.config_manager.get_all_starting_positions() + self._win_conditions_per_role = self.config_manager.get_all_win_conditions() + self._goal_description_per_role = self.config_manager.get_all_goal_descriptions() + self._steps_limit_per_role = self.config_manager.get_all_max_steps() + self.logger.debug(f"Timeouts set to:{self._steps_limit_per_role}") - if self.task_config.get_use_global_defender(): + if self.config_manager.get_use_global_defender(): self._global_defender = GlobalDefender() else: self._global_defender = None - self._use_dynamic_ips = self.task_config.get_use_dynamic_addresses() + self._use_dynamic_ips = self.config_manager.get_use_dynamic_ips() self.logger.info(f"Change IP every episode set to: {self._use_dynamic_ips}") - self._rewards = self.task_config.get_rewards(["step", "success", "fail", "false_positive"]) + self._rewards = self.config_manager.get_rewards(["step", "success", "fail", "false_positive"]) self.logger.info(f"Rewards set to:{self._rewards}") - self._min_required_players = self.task_config.get_required_num_players() + self._min_required_players = self.config_manager.get_required_num_players() self.logger.info(f"Min player requirement set to:{self._min_required_players}") # run self initialization self._initialize() diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index d8953406..b4aca4e6 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -406,7 +406,7 @@ def process_firewall()->dict: for ips in self._networks.values(): all_ips.update(ips) firewall = {ip:set() for ip in all_ips} - if self.task_config.get_use_firewall(): + if self.config_manager.get_use_firewall(): self.logger.info("Firewall enabled - processing FW rules") # LOCAL NETWORKS for net, ips in self._networks.items(): @@ -1086,7 +1086,7 @@ async def reset(self)->bool: self.logger.info('--- Reseting NSG Environment to its initial state ---') # change IPs if needed # This is done ONLY if it is (i) enabled in the task config and (ii) all agents requested it - if self.task_config.get_use_dynamic_addresses(): + if self.config_manager.get_use_dynamic_ips(): if all(self._randomize_topology_requests.values()): self.logger.info("All agents requested reset with randomized topology.") self._dynamic_ip_change() diff --git a/tests/game/test_coordinator_core.py b/tests/game/test_coordinator_core.py index 367c011a..66b63fa2 100644 --- a/tests/game/test_coordinator_core.py +++ b/tests/game/test_coordinator_core.py @@ -99,10 +99,10 @@ def _make(ip: str, port: int): @pytest.mark.asyncio async def test_load_initialization_objects_loads_config(gc_with_test_config): - """Test that loading initialization objects sets up config and cyst objects.""" - gc_with_test_config._load_initialization_objects() - assert gc_with_test_config._cyst_objects is not None - assert hasattr(gc_with_test_config, "_CONFIG_FILE_HASH") + """Test that loading initialization objects sets up config using manager.""" + await gc_with_test_config.config_manager.load() + assert gc_with_test_config.config_manager.get_cyst_objects() is not None + assert gc_with_test_config.config_manager.get_config_hash() is not None def test_convert_msg_dict_to_json_success(gc_with_test_config): """Test that convert_msg_dict_to_json correctly serializes a dictionary.""" @@ -122,59 +122,79 @@ class Unserializable: @pytest.mark.asyncio async def test_create_agent_queue_adds_new_queue(gc_with_test_config): - """Test that create_agent_queue adds a new queue for the agent.""" - agent = ("127.0.0.1", 12345) - await gc_with_test_config.create_agent_queue(agent) - assert agent in gc_with_test_config._agent_response_queues - assert isinstance(gc_with_test_config._agent_response_queues[agent], asyncio.Queue) + """Test that create_agent_queue adds a new queue for an unknown agent.""" + addr = ("127.0.0.1", 12345) + await gc_with_test_config.create_agent_queue(addr) + assert addr in gc_with_test_config._agent_response_queues + assert isinstance(gc_with_test_config._agent_response_queues[addr], asyncio.Queue) @pytest.mark.asyncio async def test_create_agent_queue_idempotent(gc_with_test_config): - """Test that create_agent_queue does not create a new queue if it already exists.""" - agent = ("127.0.0.1", 12345) - await gc_with_test_config.create_agent_queue(agent) - q1 = gc_with_test_config._agent_response_queues[agent] - await gc_with_test_config.create_agent_queue(agent) - q2 = gc_with_test_config._agent_response_queues[agent] - assert q1 is q2 + """Test that create_agent_queue doesn't recreate existing queues.""" + addr = ("127.0.0.1", 12345) + await gc_with_test_config.create_agent_queue(addr) + first_queue = gc_with_test_config._agent_response_queues[addr] + + await gc_with_test_config.create_agent_queue(addr) + second_queue = gc_with_test_config._agent_response_queues[addr] + + assert first_queue is second_queue -def test_load_initialization_objects(gc_with_test_config): - """Test that _load_initialization_objects initializes config and cyst objects.""" - gc_with_test_config._load_initialization_objects() - assert gc_with_test_config._cyst_objects is not None - assert hasattr(gc_with_test_config, "_CONFIG_FILE_HASH") +@pytest.mark.asyncio +async def test_load_initialization_objects(gc_with_test_config): + """Test that config_manager.load initializes config and cyst objects.""" + await gc_with_test_config.config_manager.load() + # Check that cyst objects are loaded via manager + assert gc_with_test_config.config_manager.get_cyst_objects() is not None + # Check that hash is set + assert gc_with_test_config.config_manager.get_config_hash() is not None -def test_get_starting_position_per_role(gc_with_test_config): - """Test that _get_starting_position_per_role returns positions for all roles.""" - gc_with_test_config._load_initialization_objects() - positions = gc_with_test_config._get_starting_position_per_role() - assert set(positions.keys()) == set(gc_with_test_config.ALLOWED_ROLES) +@pytest.mark.asyncio +async def test_start_tasks_initializes_config(gc_with_test_config): + """Test that start_tasks initializes configuration attributes via manager.""" + # We can't easily run full start_tasks because it starts a server loop. + # But we can verify that config loading logic works if we extract it or partial mock. + # Alternatively, we can test that calling config_manager.load() and then accessing properties works. + # Or, looking at previous tests, they tested the helper private methods. + # Now we should test the properties on config_manager directly OR verify they are set on GC after load. + + await gc_with_test_config.config_manager.load() + # Manually populate like start_tasks does to verify logic correctness (or assume start_tasks does it) + # Since start_tasks is the only place calling these, we might want to test the config_manager methods instead. + + positions = gc_with_test_config.config_manager.get_all_starting_positions() + assert "Attacker" in positions + assert "Defender" in positions -def test_get_goal_description_per_role(gc_with_test_config): - """Test that _get_goal_description_per_role returns descriptions for all roles.""" - gc_with_test_config._load_initialization_objects() - desc = gc_with_test_config._get_goal_description_per_role() - assert set(desc.keys()) == set(gc_with_test_config.ALLOWED_ROLES) +@pytest.mark.asyncio +async def test_goal_descriptions_loaded(gc_with_test_config): + """Test that goal descriptions are retrievable via config manager.""" + await gc_with_test_config.config_manager.load() + desc = gc_with_test_config.config_manager.get_all_goal_descriptions() + assert "Attacker" in desc + assert "Defender" in desc -def test_get_win_condition_per_role(gc_with_test_config): - """Test that _get_win_condition_per_role returns win conditions for all roles.""" - gc_with_test_config._load_initialization_objects() - win = gc_with_test_config._get_win_condition_per_role() - assert set(win.keys()) == set(gc_with_test_config.ALLOWED_ROLES) +@pytest.mark.asyncio +async def test_win_conditions_loaded(gc_with_test_config): + """Test that win conditions are retrievable via config manager.""" + await gc_with_test_config.config_manager.load() + win = gc_with_test_config.config_manager.get_all_win_conditions() + assert "Attacker" in win + assert "Defender" in win -def test_get_max_steps_per_role(gc_with_test_config): - """Test that _get_max_steps_per_role returns max steps for all roles.""" - gc_with_test_config._load_initialization_objects() - steps = gc_with_test_config._get_max_steps_per_role() +@pytest.mark.asyncio +async def test_max_steps_loaded(gc_with_test_config): + """Test that max steps are retrievable via config manager.""" + await gc_with_test_config.config_manager.load() + steps = gc_with_test_config.config_manager.get_all_max_steps() assert isinstance(steps, dict) - # values can be int or None - assert all(isinstance(v, int) or v is None for v in steps.values()) + assert "Attacker" in steps @pytest.mark.asyncio @@ -409,7 +429,6 @@ def mock_coordinator_core(self): def test_parse_action_message_valid(self, mock_coordinator_core): """New test for refactored method: _parse_action_message with valid input.""" - from netsecgame.game_components import AgentRole valid_json = '{"action_type": "ActionType.JoinGame", "parameters": {"agent_info": {"name": "TestAgent", "role": "Attacker"}}}' agent_addr = ("127.0.0.1", 12345) From 5fd492d8fbe6cd2b599aad248ad98d281650357a Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:11:28 +0100 Subject: [PATCH 096/112] Updated docsring --- netsecgame/game/coordinator.py | 25 +++++------------------ netsecgame/game/worlds/CYSTCoordinator.py | 2 +- netsecgame/game/worlds/NetSecGame.py | 4 ++-- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 10cc3aa0..faea3d04 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -37,6 +37,7 @@ class GameCoordinator: host (str): Host address for the game server. port (int): Port number for the game server. logger (logging.Logger): Logger for the GameCoordinator. + config_manager (ConfigurationManager): Manager for game configuration. _tasks (set): Set of active asyncio tasks. shutdown_flag (asyncio.Event): Event to signal shutdown. _reset_event (asyncio.Event): Event to signal game reset. @@ -46,10 +47,6 @@ class GameCoordinator: _reset_done_condition (asyncio.Condition): Condition for reset completion. _reset_lock (asyncio.Lock): Lock for reset operations. _agents_lock (asyncio.Lock): Lock for agent operations. - _service_host (str): Host for remote configuration service. - _service_port (int): Port for remote configuration service. - _task_config_file (str): Path to local task configuration file. - ALLOWED_ROLES (list): List of allowed agent roles. _cyst_objects: CYST simulator initialization objects. _cyst_object_string: String representation of CYST objects. _agent_action_queue (asyncio.Queue): Queue for agent actions. @@ -69,10 +66,10 @@ class GameCoordinator: _agent_rewards (dict): Rewards per agent address. _agent_trajectories (dict): Trajectories per agent address. """ - def __init__(self, game_host: str, game_port: int, service_host:str, service_port:int, task_config_file:str,allowed_roles=["Attacker", "Defender", "Benign"]) -> None: + def __init__(self, game_host: str, game_port: int, service_host:str, service_port:int, task_config_file:str) -> None: self.host = game_host self.port = game_port - self.logger = logging.getLogger("AIDojo-GameCoordinator") + self.logger = logging.getLogger("GameCoordinator") self._tasks = set() self.shutdown_flag = asyncio.Event() @@ -83,21 +80,10 @@ def __init__(self, game_host: str, game_port: int, service_host:str, service_por self._reset_done_condition = asyncio.Condition() self._reset_lock = asyncio.Lock() self._agents_lock = asyncio.Lock() - - # for accessing configuration remotely - self._service_host = service_host - self._service_port = service_port - # for reading configuration locally - self._task_config_file = task_config_file - + # Configuration Manager self.config_manager = ConfigurationManager(task_config_file, service_host, service_port) - - self.logger = logging.getLogger("AIDojo-GameCoordinator") - self.ALLOWED_ROLES = allowed_roles - self._cyst_objects = None - self._cyst_object_string = None - + # prepare agent communication self._agent_action_queue = asyncio.Queue() self._agent_response_queues = {} @@ -231,7 +217,6 @@ async def start_tasks(self): if self.config_manager.get_config_hash(): self._CONFIG_FILE_HASH = self.config_manager.get_config_hash() - # Read configuration # Read configuration self._starting_positions_per_role = self.config_manager.get_all_starting_positions() self._win_conditions_per_role = self.config_manager.get_all_win_conditions() diff --git a/netsecgame/game/worlds/CYSTCoordinator.py b/netsecgame/game/worlds/CYSTCoordinator.py index 2c0f388e..0e023ba6 100644 --- a/netsecgame/game/worlds/CYSTCoordinator.py +++ b/netsecgame/game/worlds/CYSTCoordinator.py @@ -41,7 +41,7 @@ def get_starting_position_from_cyst_config(cyst_objects): class CYSTCoordinator(GameCoordinator): def __init__(self, game_host:str, game_port:int, service_host:str, service_port:int, allowed_roles=["Attacker", "Defender", "Benign"]): - super().__init__(game_host, game_port, service_host, service_port, allowed_roles) + super().__init__(game_host, game_port, service_host, service_port, task_config_file=None) self._id_to_cystid = {} self._cystid_to_id = {} self._known_agent_roles = {} diff --git a/netsecgame/game/worlds/NetSecGame.py b/netsecgame/game/worlds/NetSecGame.py index b4aca4e6..94b4c9d1 100644 --- a/netsecgame/game/worlds/NetSecGame.py +++ b/netsecgame/game/worlds/NetSecGame.py @@ -20,8 +20,8 @@ class NetSecGame(GameCoordinator): - def __init__(self, game_host, game_port, task_config:str, allowed_roles=["Attacker", "Defender", "Benign"], seed=None): - super().__init__(game_host, game_port, service_host=None, service_port=None, allowed_roles=allowed_roles, task_config_file=task_config) + def __init__(self, game_host, game_port, task_config:str, seed=None): + super().__init__(game_host, game_port, service_host=None, service_port=None, task_config_file=task_config) # Internal data structure of the NSG self._ip_to_hostname = {} # Mapping of `IP`:`host_name`(str) of all nodes in the environment From 2f1d424b9cd65a691dd093fd17e43790c4b6d070 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:13:59 +0100 Subject: [PATCH 097/112] Fix logger names --- netsecgame/game/config_parser.py | 2 +- netsecgame/game/configuration_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/netsecgame/game/config_parser.py b/netsecgame/game/config_parser.py index db67c74e..8aace5ec 100644 --- a/netsecgame/game/config_parser.py +++ b/netsecgame/game/config_parser.py @@ -22,7 +22,7 @@ def __init__(self, task_config_file:str|None=None, config_dict:dict|None=None): """ Initializes the configuration parser. Required either path to a confgiuration file or a dict with configuraitons. """ - self.logger = logging.getLogger('configparser') + self.logger = logging.getLogger('ConfigParser') if task_config_file: self.read_config_file(task_config_file) elif config_dict: diff --git a/netsecgame/game/configuration_manager.py b/netsecgame/game/configuration_manager.py index 9b68aaf1..c5e63260 100644 --- a/netsecgame/game/configuration_manager.py +++ b/netsecgame/game/configuration_manager.py @@ -17,7 +17,7 @@ class ConfigurationManager: """ def __init__(self, task_config_file: Optional[str] = None, service_host: Optional[str] = None, service_port: Optional[int] = None): - self.logger = logging.getLogger("GameCoordinator-ConfigurationManager") + self.logger = logging.getLogger("ConfigurationManager") self._task_config_file = task_config_file self._service_host = service_host self._service_port = service_port From e7eafb02a1a90afa12a461b1b985d2d62ae4b6f2 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:25:45 +0100 Subject: [PATCH 098/112] Fix trajectory recording options --- netsecgame/game/configuration_manager.py | 7 ++++++- netsecgame/game/coordinator.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/netsecgame/game/configuration_manager.py b/netsecgame/game/configuration_manager.py index c5e63260..16ab1841 100644 --- a/netsecgame/game/configuration_manager.py +++ b/netsecgame/game/configuration_manager.py @@ -152,7 +152,7 @@ def get_required_num_players(self, default_value: int = 1) -> int: raise RuntimeError("Configuration not loaded.") return self._parser.get_required_num_players(default_value) - def get_use_firewall(self, default_value: bool = False) -> bool: + def get_use_firewall(self, default_value: bool = True) -> bool: if not self._parser: raise RuntimeError("Configuration not loaded.") return self._parser.get_use_firewall(default_value) @@ -198,3 +198,8 @@ def get_all_max_steps(self) -> Dict[str, Optional[int]]: # But wait, self.get_max_steps(role) does `self._parser.get_max_steps(role)` already. # Iterating over AgentRole is correct. return {role: self.get_max_steps(role) for role in AgentRole} + + def get_store_trajectories(self, default_value: bool = False) -> bool: + if not self._parser: + raise RuntimeError("Configuration not loaded.") + return self._parser.get_store_trajectories(default_value) diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index faea3d04..9894af23 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -589,7 +589,7 @@ async def _reset_game(self): self.logger.info("Resetting game to initial state.") await self.reset() for agent in self.agents: - if self.task_config.get_store_trajectories(): + if self.config_manager.get_store_trajectories(): async with self._agents_lock: self._store_trajectory_to_file(agent) self.logger.debug(f"Resetting agent {agent}") @@ -868,4 +868,5 @@ def is_agent_benign(self, agent_addr:tuple)->bool: """ if agent_addr not in self.agents: return False + #TODO: change to use AgentRole return self.agents[agent_addr][1].lower() in ["defender", "benign"] \ No newline at end of file From 217e90ecd8379ca2a2e392a4968a8a2e10ab36f2 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:27:28 +0100 Subject: [PATCH 099/112] rename logger --- netsecgame/game/agent_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netsecgame/game/agent_server.py b/netsecgame/game/agent_server.py index 550ae9e0..d26b2778 100644 --- a/netsecgame/game/agent_server.py +++ b/netsecgame/game/agent_server.py @@ -26,7 +26,7 @@ def __init__(self, actions_queue, agent_response_queues, max_connections): self.answers_queues = agent_response_queues self.max_connections = max_connections self.current_connections = 0 - self.logger = logging.getLogger("AIDojo-AgentServer") + self.logger = logging.getLogger("AgentServer") async def handle_agent_quit(self, peername:tuple): """ From 40e59a87791421060353b4846cdc78107c25d6b7 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:49:28 +0100 Subject: [PATCH 100/112] Fix typos --- docs/architecture.md | 27 ++++++++++---------- docs/configuration.md | 57 +++++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index ff2d05f3..1c1fb44e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,11 +1,11 @@ -## NetSecGame Architecture -The Network Security Game(NSG) works as a game server - agents connect to it via TCP sockets and interact with the environment using the standard RL communication loop: Agent submits actinon and recieves new observation of the environment. The NSG supports real-time, highly customizable multi-agent simulations. +# NetSecGame Architecture +The NetSecGame (NSG) works as a game server - agents connect to it via TCP sockets and interact with the environment using the standard RL communication loop: Agent submits action and receives new observation of the environment. The NSG supports real-time, highly customizable multi-agent simulations. ## Game Components The following classes are used in the game to hold information about the state of the game. They are used both in the [Actions](#actions) and [GameState](#gamestate). See the API Reference for [GameComponents](game_components.md) ### Building blocks #### IP -IP is immutable object that represents an IPv4 object in the NetSecGame. It has a single parameter of the address in a dot-decimal notation (4 octet represeted as decimal value separeted by dots). +IP is immutable object that represents an IPv4 object in the NetSecGame. It has a single parameter of the address in a dot-decimal notation (4 octet represented as decimal value separated by dots). Example: ```python @@ -24,9 +24,9 @@ net = Network("192.168.1.0", 24) #### Service Service class holds information about services running in hosts. Each Service has four parameters: - `name`:str - Name of the service (e.g., "SSH") -- `type`:str - `passive` or `active`. Currently not being used. +- `type`:str - `passive`, `active` or `unknown`(default). - `version`:str - version of the service. -- `is_local`:bool - flag specifying if the service is local only. (if `True`, service is NOT visible without controlling the host). +- `is_local`:bool - flag specifying if the service is local only (default=True). (if `True`, service is NOT visible without controlling the host). Example: ```python @@ -52,7 +52,7 @@ d2 = Data("User1", "DatabaseData", size=42, type="txt", "SecretUserDatabase") GameState is an object that represents a view of the NetSecGame environment in a given state. It is constructed as a collection of 'assets' available to the agent. GameState has following parts: - `known_networks`: Set of [Network](#network) objects that the agent is aware of - `known_hosts`: Set of [IP](#ip) objects that the agent is aware of -- `controlled_hosts`: Set of [IP](#ip) objetcs that the agent has control over. Note that `controlled_hosts` is a subset of `known_hosts`. +- `controlled_hosts`: Set of [IP](#ip) objects that the agent has control over. Note that `controlled_hosts` is a subset of `known_hosts`. - `known_services`: Dictionary of services that the agent is aware of. The dictionary format: {`IP`: {`Service`}} where [IP](#ip) object is a key and the value is a set of [Service](#service) objects located in the `IP`. - `known_data`: Dictionary of data instances that the agent is aware of. The dictionary format: {`IP`: {`Data`}} where [IP](#ip) object is a key and the value is a set of [Data](#data) objects located in the `IP`. @@ -79,20 +79,21 @@ The Action consists of two parts - **ScanNetwork**, params{`source_host`:``, `target_network`:``}: Scans the given `` from a specified source host. Discovers ALL hosts in a network that are accessible from ``. If successful, returns set of discovered `` objects. - **FindServices**, params={`source_host`:``, `target_host`:``}: Used to discover ALL services running in the `target_host` if the host is accessible from `source_host`. If successful, returns a set of all discovered `` objects. - **FindData**, params={`source_host`:``, `target_host`:``}: Searches `target_host` for data. If `source_host` differs from `target_host`, success depends on accessability from the `source_host`. If successful, returns a set of all discovered `` objects. -- **ExploitService**, params={`source_host`:``, `target_host`:``, `taget_service`:``}: Exploits `target_service` in a specified `target_host`. If successful, the attacker gains control of the `target_host`. +- **ExploitService**, params={`source_host`:``, `target_host`:``, `target_service`:``}: Exploits `target_service` in a specified `target_host`. If successful, the attacker gains control of the `target_host`. - **ExfiltrateData**, params{`source_host`:``, `target_host`:``, `data`:``}: Copies `data` from the `source_host` to `target_host` IF both are controlled and `target_host` is accessible from `source_host`. +- **BlockIP**, params{`source_host`:``, `target_host`:``, `blocked_host`:``}: Blocks communication from/to `blocked_host` on `target_host`. Requires control of `target_host`. ### Action preconditions and effects In the following table, we describe the effects of selected actions and their preconditions. Note that if the preconditions are not satisfied, the actions's effects are not applied. | Action | Params | Preconditions | Effects | |----------------------|----------------------|----------------------|----------------------| -| ScanNetwork| `source_host`, `target_network`| `source_host` ∈ `controlled_hosts`| extends `known_networks`| -|FindServices| `source_host`, `target_host`| `source_host` ∈ `controlled_hosts`| extends `known_services` AND `known_hosts`| +| ScanNetwork| `source_host`, `target_network`| `source_host` ∈ `controlled_hosts`| extends `known_networks`| +|FindServices| `source_host`, `target_host`| `source_host` ∈ `controlled_hosts`| extends `known_services` AND `known_hosts`| |FindData| `source_host`, `target_host`| `source_host`, `target_host` ∈ `controlled_hosts`| extends `known_data`| -|Exploit Service | `source_host`, `target_host`, `target_service`|`source_host` ∈ `controlled_hosts`| extends `controlled_hosts` with `target_host`| -ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host` ∈ `controlled_hosts` AND `data` ∈ `known_data`| extends `known_data[target_host]` with `data`| -|BlockIP | `source_host`, `target_host`, `blockedIP`|`source_host` ∈ `controlled_hosts`| extends `known_blocks[target_host]` with `blockedIP`| +|Exploit Service | `source_host`, `target_host`, `target_service`|`source_host` ∈ `controlled_hosts`| extends `controlled_hosts` with `target_host`| +|ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host` ∈ `controlled_hosts` AND `data` ∈ `known_data`| extends `known_data[target_host]` with `data`| +|BlockIP | `source_host`, `target_host`, `blocked_host`|`source_host` ∈ `controlled_hosts`| extends `known_blocks[target_host]` with `blocked_host`| #### Assumption and Conditions for Actions 1. When playing the `ExploitService` action, it is expected that the agent has discovered this service before (by playing `FindServices` in the `target_host` before this action) @@ -100,7 +101,7 @@ ExfiltrateData| `source_host`,`target_host`, `data` |`source_host`, `target_host 3. The `Find Data` action requires ownership of the target host. 4. Playing `ExfiltrateData` requires controlling **BOTH** source and target hosts 5. Playing `Find Services` can be used to discover hosts (if those have any active services) -6. Parameters of `ScanNetwork` and `FindServices` can be chosen arbitrarily (they don't have to be listed in `known_newtworks`/`known_hosts`) +6. Parameters of `ScanNetwork` and `FindServices` can be chosen arbitrarily (they don't have to be listed in `known_networks`/`known_hosts`) ### Observations After submitting Action `a` to the environment, agents receive an `Observation` in return. Each observation consists of 4 parts: diff --git a/docs/configuration.md b/docs/configuration.md index 02d93625..ae40be5c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -63,39 +63,41 @@ There are 5 topologies available in NSG: In summary, the topology change (IP randomization) can't change without allowing it in the task configuration. If allowed in the task config YAML, it can still be rejected by the agents. - `use_firewall` - if `True` firewall rules defined in `scenario` are used when executing actions. When `False`, the firewall is ignored, and all connections are allowed (Default) -- `use_global_defender` - if `True`, enables global defendr which is part of the environment and can stop interaction of any playing agent. +- `use_global_defender` - if `True`, enables global defender which is part of the environment and can stop interaction of any playing agent. - `required_players` - Minimum required players for the game to start (default 1) - `rewards`: - `success` - sets reward which agent gets when it reaches the goal (default 100) - `fail` - sets the reward that which agent does not reach it's objective (default -10) - - `step_reward` - sets reward which agent gets for every step taken (default -1) + - `step` - sets reward which agent gets for every step taken (default -1) + - `false_positive` - sets reward for a false positive action (default -5) - `actions` - defines the probability of success for every ActionType ```YAML env: random_seed: 'random' - scenario: 'scenario1' + scenario: 'two_networks_tiny' use_global_defender: False use_dynamic_addresses: False use_firewall: True save_trajectories: False rewards: - win: 100 + success: 100 step: -1 - loss: -10 + fail: -10 + false_positive: -5 actions: scan_network: - prob_success: 1.0 + prob_success: 1.0 find_services: - prob_success: 1.0 + prob_success: 1.0 exploit_service: - prob_success: 1.0 + prob_success: 1.0 find_data: - prob_success: 1.0 + prob_success: 1.0 exfiltrate_data: - prob_success: 1.0 + prob_success: 1.0 block_ip: - prob_success: 1.0 + prob_success: 1.0 ``` ### Definition of the network topology The network topology and rules are defined using a [CYST](https://pypi.org/project/cyst/) simulator configuration. Cyst defines a complex network configuration, and this environment does not use all Cyst features for now. CYST components currently used are: @@ -156,24 +158,25 @@ coordinator: Attacker: max_steps: 20 goal: - randomize_goal_every_episode: False - known_networks: [] - known_hosts: [] - controlled_hosts: [] - known_services: {192.168.1.3: [Local system, lanman server, 10.0.19041, False], 192.168.1.4: [Other system, SMB server, 21.2.39421, False]} - known_data: {213.47.23.195: ["random"]} - known_blocks: {'all_routers': 'all_attackers'} + description: "Exfiltrate data from Samba server to remote C&C server." + is_any_part_of_goal_random: True + known_networks: [] + known_hosts: [] + controlled_hosts: [] + known_services: {} + known_data: {213.47.23.195: [[User1,DataFromServer1]]} + known_blocks: {} start_position: - known_networks: [] - known_hosts: [] - # The attacker must always at least control the CC if the goal is to exfiltrate there - # Example of fixing the starting point of the agent in a local host - controlled_hosts: [213.47.23.195, random] - # Services are defined as a target host where the service must be, and then a description in the form 'name, type, version, is_local' - known_services: {} - known_data: {} - known_blocks: {} + known_networks: [] + known_hosts: [] + # The attacker must always at least control the CC if the goal is to exfiltrate there + # Example of fixing the starting point of the agent in a local host + controlled_hosts: [213.47.23.195, random] + # Services are defined as a target host where the service must be, and then a description in the form 'name, type, version, is_local' + known_services: {} + known_data: {} + known_blocks: {} ``` ### Defender configuration From ca0bdac5a6c0901bf388fc2f08caddf0104d6c26 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:51:56 +0100 Subject: [PATCH 101/112] fix path --- docs/agent_server.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/agent_server.md diff --git a/docs/agent_server.md b/docs/agent_server.md new file mode 100644 index 00000000..0fba5532 --- /dev/null +++ b/docs/agent_server.md @@ -0,0 +1 @@ +::: netsecgame.game.agent_server.AgentServer \ No newline at end of file From 6cd75ec09639b427e913375085676c9b06780464 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:54:31 +0100 Subject: [PATCH 102/112] updated docs --- docs/configuration_manager.md | 11 +++++++++++ docs/game_coordinator.md | 18 +++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 docs/configuration_manager.md diff --git a/docs/configuration_manager.md b/docs/configuration_manager.md new file mode 100644 index 00000000..98c22770 --- /dev/null +++ b/docs/configuration_manager.md @@ -0,0 +1,11 @@ +## Configuration Manager + +Configuration manager is a component of the game coordinator that handles the configuration of the game. It is responsible for loading the configuration from the YAML file and providing it to the game coordinator. + +::: netsecgame.game.configuration_manager.ConfigurationManager + +## ConfigParser + +ConfigParser is a class that is responsible for parsing the YAML configuration file and providing it to the game coordinator. + +::: netsecgame.game.config_parser.ConfigParser \ No newline at end of file diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md index d600197c..2c639340 100644 --- a/docs/game_coordinator.md +++ b/docs/game_coordinator.md @@ -3,26 +3,26 @@ Coordinator is the centerpiece of the game orchestration. It provides an interfa In detail it handles: -1. World initialiazation +1. World initialization 2. Registration of new agents in the game 3. Agent-World communication (message verification and forwarding) 4. Recording (and storing) trajectories of agents (optional) -4. Detection of episode ends (either by reaching timout or agents reaching their respective goals) -5. Assigning rewards for each action and at the end of each episode -6. Removing agents from the game -7. Registering the GameReset requests and handelling the game resets. +5. Detection of episode ends (either by reaching timeout or agents reaching their respective goals) +6. Assigning rewards for each action and at the end of each episode +7. Removing agents from the game +8. Registering the GameReset requests and handling the game resets. To facilitate the communication the coordinator uses a TCP server to which agents connect. The communication is asynchronous and depends of the -## Connction to other game components -Coordinator, having the role of the middle man in all communication between the agent and the world uses several queues for massing passing and handelling. +## Connection to other game components +Coordinator, having the role of the middle man in all communication between the agent and the world uses several queues for message passing and handling. 1. `Action queue` is a queue in which the agents submit their actions. It provides N:1 communication channel in which the coordinator receives the inputs. -2. `Answer queues` is a separeate queue **per agent** in which the results of the actions are send to the agent. +2. `Answer queues` is a separate queue **per agent** in which the results of the actions are send to the agent. ## Episode The episode starts with sufficient amount of agents registering in the game. Each agent role has a maximum allowed number of steps defined in the task configuration. An episode ends if all agents reach the goal -::: netsecgame.game.coordinator.AgentServer + ::: netsecgame.game.coordinator.GameCoordinator ::: netsecgame.game.worlds.NetSecGame.NetSecGame \ No newline at end of file From 720f8355560c3e29b3f4c212f5c3cb336b4657c5 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 15:55:14 +0100 Subject: [PATCH 103/112] fixed index --- docs/index.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/index.md b/docs/index.md index 54d590d8..c9ab1a13 100644 --- a/docs/index.md +++ b/docs/index.md @@ -147,29 +147,33 @@ Upon which the game server is created on `localhost:9000` to which the agents ca ### Components of the NetSecGame Environment The NetSecGame has several components in the following files: ``` -├── NetSecgame/ +├── netsecgame/ | ├── agents/ | ├── base_agent.py # Basic agent class. Defines the API for agent-server communication | ├── game/ | ├── scenarios/ -| ├── tiny_scenario_configuration.py -| ├── smaller_scenario_configuration.py -| ├── scenario_configuration.py -| ├── three_net_configuration.py +| ├── three_net_scenario.py +| ├── two_nets.py +| ├── two_nets_tiny.py +| ├── two_nets_small.py +| ├── one_net.py | ├── worlds/ | ├── NetSecGame.py # (NSG) basic simulation | ├── RealWorldNetSecGame.py # Extension of `NSG` - runs actions in the *network of the host computer* | ├── CYSTCoordinator.py # Extension of `NSG` - runs simulation in CYST engine. | ├── WhiteBoxNetSecGame.py # Extension of `NSG` - provides agents with full list of actions upon registration. | ├── config_parser.py # NSG task configuration parser +| ├── configuration_manager.py # Manages the loading and access of game configuration. | ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) +| ├── agent_server.py # Class used for serving the agents when connecting to the game run by the GameCoordinator. | ├── global_defender.py # Stochastic (non-agentic defender) | ├── game_components.py # contains basic building blocks of the environment | ├── utils/ | ├── utils.py -| ├── log_parser.py +| ├── trajectory_recorder.py +| ├── trajectory_analysis.py +| ├── aidojo_log_colorizer.py | ├── gamaplay_graphs.py -| ├── actions_parser.py ``` Some compoments are described in detail in following sections: From b1fb906564fba61a0a02f1a252db6c6f69997df7 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 16:20:24 +0100 Subject: [PATCH 104/112] Updated README --- README.md | 216 ++++-------------------------------------------------- 1 file changed, 13 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index 2556bd22..6d42ed0f 100755 --- a/README.md +++ b/README.md @@ -110,6 +110,9 @@ env: # Environment configuraion fail: -10 false_positive: -5 ``` +For detailed configuration instructions, please refer to the [Configuration Documentation](https://stratosphereips.github.io/NetSecGame/configuration/). + + ### Starting the NetSecGame With the configuration ready the environment can be started in selected port #### In Docker container @@ -157,7 +160,7 @@ You can find user documentation at [https://stratosphereips.github.io/NetSecGame The architecture of the environment can be seen [here](docs/Architecture.md). The NetSecGame environment has several components in the following files: ``` -├── NetSecgame/ +├── netsecgame/ | ├── agents/ | ├── base_agent.py # Basic agent class. Defines the API for agent-server communication | ├── game/ @@ -165,13 +168,15 @@ The NetSecGame environment has several components in the following files: | ├── tiny_scenario_configuration.py | ├── smaller_scenario_configuration.py | ├── scenario_configuration.py -| ├── three_net_configuration.py +| ├── three_net_scenario.py | ├── worlds/ | ├── NetSecGame.py # (NSG) basic simulation | ├── RealWorldNetSecGame.py # Extension of `NSG` - runs actions in the *network of the host computer* | ├── CYSTCoordinator.py # Extension of `NSG` - runs simulation in CYST engine. | ├── WhiteBoxNetSecGame.py # Extension of `NSG` - provides agents with full list of actions upon registration. +| ├── agent_server.py # Agent server implementation | ├── config_parser.py # NSG task configuration parser +| ├── configuration_manager.py # Helper tool to collect and parse query configuration of the game. | ├── coordinator.py # Core game server. Not to be run as stand-alone world (see worlds/) | ├── global_defender.py # Stochastic (non-agentic defender) | ├── game_components.py # contains basic building blocks of the environment @@ -180,16 +185,17 @@ The NetSecGame environment has several components in the following files: | ├── log_parser.py | ├── gamaplay_graphs.py | ├── actions_parser.py + ``` #### Directory Details - `coordinator.py`: Basic coordinator class. Handles agent communication and coordination. **Does not implement dynamics of the world** and must be extended (see examples in `worlds/`). -- `game_components.py`: Implements a library with objects used in the environment. See [detailed explanation](AIDojoCoordinator/docs/Components.md) of the game components. +- `game_components.py`: Implements a library with objects used in the environment. See [detailed explanation](./docs/game_components.md) of the game components. - `global_defender.py`: Implements a global (omnipresent) defender that can be used to stop agents. Simulation of SIEM. ##### **`worlds/`** Modules for different world configurations: -- `NSGCoordinator.py`: Coordinator for the Network Security Game. -- `NSGRealWorldCoordinator.py`: Real-world NSG coordinator (actions are executed in the *real network*). +- `NetSecGame.py`: Coordinator for the Network Security Game. +- `RealWorldNetSecGame.py`: Real-world NSG coordinator (actions are executed in the *real network*). - `CYSTCoordinator.py`: Coordinator for CYST-based simulations (requires CYST running). ##### **`scenarios/`** @@ -197,7 +203,8 @@ Predefined scenario configurations: - `tiny_scenario_configuration.py`: A minimal example scenario. - `smaller_scenario_configuration.py`: A compact scenario configuration used for development and rapid testing. - `scenario_configuration.py`: The main scenario configuration. -- `three_net_configuration.py`: Configuration for a three-network scenario. Used for the evaluation of the model overfitting. +- `three_net_scenario.py`: Configuration for a three-network scenario. Used for the evaluation of the model overfitting. + Implements the network game's configuration of hosts, data, services, and connections. It is taken from [CYST](https://pypi.org/project/cyst/). ##### **`utils/`** @@ -303,207 +310,10 @@ The system monitors actions and maintains a history of recent ones within the ti This approach ensures that only repeated or excessive behavior is flagged, reducing false positives while maintaining a realistic monitoring system. -## Starting the game -The environment should be created before starting the agents. The properties of the game, the task and the topology can be either read from a local file or via REST request to the GameDashboard. - -#### To start the game with a local configuration file -```python3 -m AIDojoCoordinator.worlds.NSEGameCoordinator --task_config=``` - -#### To start the game with a remotely defined configuration -```python3 -m AIDojoCoordinator.worlds.CYSTCoordinator --service_host= --service_port= ``` - -When created, the environment: -1. reads the configuration file -2. loads the network configuration from the config file -3. reads the defender type from the configuration -4. creates starting position and goal position following the config file -5. starts the game server in a specified address and port ### Interaction with the Environment When the game server is created, [agents](https://github.com/stratosphereips/NetSecGameAgents/tree/main) connect to it and interact with the environment. In every step of the interaction, agents submits an [Action](./AIDojoCoordinator/docs/Components.md#actions) and receive [Observation](./AIDojoCoordinator/docs/Components.md#observations) with `next_state`, `reward`, `is_terminal`, `end`, and `info` values. Once the terminal state or timeout is reached, no more interaction is possible until the agent asks for a game reset. Each agent should extend the `BaseAgent` class in [agents](https://github.com/stratosphereips/NetSecGameAgents/tree/main). - -### Configuration -The NetSecEnv is highly configurable in terms of the properties of the world, tasks, and agent interaction. Modification of the world is done in the YAML configuration file in two main areas: -1. Environment (`env` section) controls the properties of the world (taxonomy of networks, maximum allowed steps per episode, probabilities of action success, etc.) -2. Task configuration defines the agents' properties (starting position, goal) - -#### Environment configuration -The environment part defines the properties of the environment for the task (see the example below). In particular: -- `random_seed` - sets the seed for any random processes in the environment -- `scenario` - sets the scenario (network topology) used in the task (currently, `scenario1_tiny`, `scenario1_small`, `scenario1` and `three_nets` are available) -- `save_tajectories` - if `True`, interaction of the agents is serialized and stored in a file -- `use_dynamic_addresses` - if `True`, the network and IP addresses defined in `scenario` are randomly changed at the beginning of **EVERY** episode (the network topology is kept as defined in the `scenario`. Relations between networks are kept, IPs inside networks are chosen at random based on the network IP and mask) -- `use_firewall` - if `True`, firewall rules defined in `scenario` are used when executing actions. When `False`, the firewall is ignored, and all connections are allowed (Default) -- `use_global_defender` - if `True`, enables global defender, which is part of the environment and can stop interaction of any playing agent. -- `required_players` - Minimum required players for the game to start (default 1) -- `rewards`: - - `success` - sets the reward when the agent reaches the goal (default 100) - - `fail` - sets the reward when the agent does not reach its objective (default -10) - - `step_reward` - sets the reward when the agent does each single step in the game (default -1) -- `actions` - defines the probability of success for every ActionType - -```YAML -env: - random_seed: 'random' - scenario: 'scenario1' - use_global_defender: False - use_dynamic_addresses: False - use_firewall: True - save_trajectories: False - rewards: - win: 100 - step: -1 - loss: -10 - actions: - scan_network: - prob_success: 1.0 - find_services: - prob_success: 1.0 - exploit_service: - prob_success: 1.0 - find_data: - prob_success: 1.0 - exfiltrate_data: - prob_success: 1.0 - block_ip: - prob_success: 1.0 -``` - -#### Task configuration -The task configuration part (section `coordinator[agents]`) defines the starting and goal position of the attacker and the type of defender that is used. - -##### Attacker configuration (`[coordinator][agents][Attacker]`) -Configuration of the attacking agents. Consists of three parts: -1. Goal definition (`goal`) which describes the `GameState` properties that must be fulfilled to award `win` reward to the attacker: - - `known_networks:`(list) - - `known_hosts`(list) - - `controlled_hosts`(list) - - `known_services`(dict) - - `known_data`(dict) - - `known_blocks`(dict) - - Each of the parts can be empty (not part of the goal, exactly defined (e.g., `known_networks: [192.168.1.0/24, 192.168.3.0/24]`) or include the keyword `random` (`controlled_hosts: [213.47.23.195, random]`, `known_data: {213.47.23.195: [random]}`. - Additionally, if `random` keyword is used in the goal definition, - `randomize_goal_every_episode`. If set to `True`, each keyword `random` is replaced with a randomly selected, valid option at the beginning of **EVERY** episode. If set to `False`, randomization is performed only **once** when the environment is -2. Definition of starting position (`start_position`), which describes the `GameState` in which the attacker starts. It consists of: - - `known_networks:`(list) - - `known_hosts`(list) - - `controlled_hosts`(list) - - `known_services`(dict) - - `known_data`(dict) - - `known_blocks`(dict) - - The initial network configuration must assign at least **one** controlled host to the attacker in the network. Any item in `controlled_hosts` is copied to `known_hosts`, so there is no need to include these in both sets. `known_networks` is also extended with a set of **all** networks accessible from the `controlled_hosts` -3. Definition of maximum allowed number of steps: - - `max_steps:`(int): defines the maximum allowed number of steps for attackers in **each** episode. - -Example attacker configuration: -```YAML -coordinator: - agents: - Attacker: - max_steps: 20 - goal: - randomize_goal_every_episode: False - known_networks: [] - known_hosts: [] - controlled_hosts: [] - known_services: {192.168.1.3: [Local system, lanman server, 10.0.19041, False], 192.168.1.4: [Other system, SMB server, 21.2.39421, False]} - known_data: {213.47.23.195: ["random"]} - known_blocks: {'all_routers': 'all_attackers'} - - start_position: - known_networks: [] - known_hosts: [] - # The attacker must always at least control the CC if the goal is to exfiltrate there - # Example of fixing the starting point of the agent in a local host - controlled_hosts: [213.47.23.195, random] - # Services are defined as a target host where the service must be, and then a description in the form 'name, type, version, is_local' - known_services: {} - known_data: {} - known_blocks: {} -``` - -##### Defender configuration (`[coordinator][agents][Defender]`) -Currently, the defender **is** a separate agent. - -If you want a defender in the game, you must connect a defender agent. For playing without a defender, leave the section empty. - -Example of defender configuration: -```YAML - Defender: - goal: - description: "Block all attackers" - known_networks: [] - known_hosts: [] - controlled_hosts: [] - known_services: {} - known_data: {} - known_blocks: {} - - start_position: - known_networks: [] - known_hosts: [] - controlled_hosts: [all_local] - known_services: {} - known_data: {} - blocked_ips: {} - known_blocks: {} -``` -As in other agents, the description is only a text for the agent, so it can know what is supposed to do to win. In the current implementation, the *Defender* wins, if **NO ATTACKER** reaches their goal. - - -### Definition of the network topology -The network topology and rules are defined using a [CYST](https://pypi.org/project/cyst/) simulator configuration. Cyst defines a complex network configuration, and this environment does not use all Cyst features for now. CYST components currently used are: - -- Server hosts (are a NodeConf in CYST) - - Interfaces, each with one IP address - - Users who can log in to the host - - Active and passive services - - Data in the server - - To which network is connected -- Client host (are of type Node in CYST) - - Interfaces, each with one IP address - - To which network is connected - - Active and passive services, if any - - Data in the client -- Router (are of type RouterConf in CYST) - - Interfaces, each with one IP address - - Networks - - Allowed connections between hosts -- Internet host (as an external router) (are of type Node in RouterConf) - - Interfaces, each with one IP address - - Which host can connect -- Exploits - - Which service is the exploit linked to - -### Scenarios -In the current state, we support a single scenario: Data exfiltration to a remote C&C server. However, extensions can be made by modification of the task configuration. - -#### Data exfiltration to a remote C&C -For the data exfiltration, we support 3 variants. The full scenario contains 5 clients (where the attacker can start) and 5 servers, where the data that is supposed to be exfiltrated can be located. *scenario1_small* is a variant with a single client (the attacker always starts there) and all 5 servers. *scenario1_tiny* contains only a single server with data. The tiny scenario is trivial and intended only for debugging purposes. - - - - - - - -
Scenario 1Scenario 1 - smallScenario 1 -tiny
Scenario 1 - Data exfiltrationScenario 1 - smallScenario 1 - tiny
3-nets scenario
- Scenario 1 - Data exfiltration -
- -### Trajectory storing and analysis -The trajectory is a sequence of GameStates, Actions, and rewards in one run of a game. It contains the complete information of the actions played by the agent, the rewards observed and their effect on the state of the environment. -Trajectories performed by the agents can be stored in a file using the following configuration (on the server side): -```YAML -env: - save_trajectories: True -``` -> [!CAUTION] -> Trajectory files can grow very fast. It is recommended to use this feature on evaluation/testing runs only. By default, this feature is not enabled. - ## Testing the environment It is advised that after every change, you test if the env is running correctly by doing From 67d32e70e25a306c0dcf8172d047a820855ab638 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 16:20:36 +0100 Subject: [PATCH 105/112] Added API refereces link --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index cd9b945e..58303545 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,8 @@ nav: - API Reference: - game_components.md - game_coordinator.md + - agent_server.md + - configuration_manager.md plugins: - mkdocstrings: From 9a56d7c0a8b8bc82b4cecfb6efd558a53859295b Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 16:42:36 +0100 Subject: [PATCH 106/112] change name --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7cef0642..4062e5c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "netsecgame-test" +name = "netsecgame" dynamic = ["version"] description = "A package for coordinating AI-driven network simulation games." readme = "README.md" @@ -75,6 +75,8 @@ exclude = [ "NetSecGameAgents*" ] + + [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] From 85c7594d5d101a41c3d9e7f879f2ad5b4860e15f Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 16:42:48 +0100 Subject: [PATCH 107/112] exclude files that are outdated/not ready yet --- MANIFEST.in | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..2410222d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +exclude netsecgame/game/worlds/CYSTCoordinator.py +exclude netsecgame/game/worlds/RealWorldNetSecGame.py +exclude netsecgame/utils/trajectory_analysis.py +exclude netsecgame/utils/actions_parser.py +exclude netsecgame/utils/gamaplay_graphs.py +exclude netsecgame/utils/log_parser.py From 0c44959b0caaa38d64dc11652ed888f4b3f13211 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 16:52:26 +0100 Subject: [PATCH 108/112] fix tests --- tests/game/test_coordinator_core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/game/test_coordinator_core.py b/tests/game/test_coordinator_core.py index 66b63fa2..9058a6a0 100644 --- a/tests/game/test_coordinator_core.py +++ b/tests/game/test_coordinator_core.py @@ -6,7 +6,7 @@ from types import SimpleNamespace from netsecgame.game.coordinator import GameCoordinator -from netsecgame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus +from netsecgame.game_components import ActionType, Action, AgentStatus, GameState, Observation, GameStatus, AgentRole from netsecgame.game.coordinator import convert_msg_dict_to_json # ----------------------- @@ -45,7 +45,6 @@ def gc_with_test_config(test_config_file_path): game_port=9999, service_host=None, # force local config loading service_port=0, - allowed_roles=["Attacker", "Defender", "Benign"], task_config_file=test_config_file_path, ) From c1ce71b37f19ce5e9f31cf28530a5cf0c487e26a Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 17:06:36 +0100 Subject: [PATCH 109/112] fix ruff errors --- netsecgame/game/configuration_manager.py | 1 - netsecgame/game/coordinator.py | 7 ++----- netsecgame/game_components.py | 3 +-- netsecgame/utils/trajectory_recorder.py | 2 +- tests/utils/test_trajectory_recorder.py | 6 +++--- tests/utils/test_utils.py | 3 --- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/netsecgame/game/configuration_manager.py b/netsecgame/game/configuration_manager.py index 16ab1841..4a5ff752 100644 --- a/netsecgame/game/configuration_manager.py +++ b/netsecgame/game/configuration_manager.py @@ -1,6 +1,5 @@ import logging from typing import Optional, Dict, Any, List -import asyncio from aiohttp import ClientSession from netsecgame.game.config_parser import ConfigParser diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index 9894af23..904e3444 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -5,15 +5,12 @@ from typing import Optional import signal import os -from aiohttp import ClientSession -from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, ProtocolConfig, AgentRole +from netsecgame.game_components import Action, Observation, ActionType, GameStatus, GameState, AgentStatus, AgentRole from netsecgame.game.global_defender import GlobalDefender -from netsecgame.utils.utils import observation_as_dict, get_str_hash, store_trajectories_to_jsonl -from netsecgame.game.config_parser import ConfigParser +from netsecgame.utils.utils import observation_as_dict,store_trajectories_to_jsonl from netsecgame.game.agent_server import AgentServer from netsecgame.game.configuration_manager import ConfigurationManager -from cyst.api.environment.environment import Environment def convert_msg_dict_to_json(msg_dict: dict) -> str: diff --git a/netsecgame/game_components.py b/netsecgame/game_components.py index 3db17e21..4b3d3ec1 100755 --- a/netsecgame/game_components.py +++ b/netsecgame/game_components.py @@ -2,9 +2,8 @@ # Author Ondrej Lukas - ondrej.lukas@aic.fel.cvut.cz # Library of helpful functions and objects to play the net sec game from dataclasses import dataclass, field, asdict -from typing import Dict, Any, List, Set, Tuple, Optional, NamedTuple +from typing import Dict, Any, List, Set, Tuple, NamedTuple import dataclasses -from collections import namedtuple import json import enum import sys diff --git a/netsecgame/utils/trajectory_recorder.py b/netsecgame/utils/trajectory_recorder.py index 00bacee0..bf0b4231 100644 --- a/netsecgame/utils/trajectory_recorder.py +++ b/netsecgame/utils/trajectory_recorder.py @@ -3,7 +3,7 @@ import os import logging from datetime import datetime -from typing import Optional, Dict, Any, List +from typing import Optional, Dict, Any from netsecgame.game_components import Action, GameState from netsecgame.utils.utils import store_trajectories_to_jsonl diff --git a/tests/utils/test_trajectory_recorder.py b/tests/utils/test_trajectory_recorder.py index 7d281cbd..0e80aefb 100644 --- a/tests/utils/test_trajectory_recorder.py +++ b/tests/utils/test_trajectory_recorder.py @@ -1,7 +1,7 @@ import pytest -from unittest.mock import MagicMock, patch -from netsecgame import TrajectoryRecorder -from netsecgame.game_components import Action, ActionType, GameState, Network +from unittest.mock import patch +from netsecgame.utils.trajectory_recorder import TrajectoryRecorder +from netsecgame.game_components import Action, ActionType, GameState # Mock objects needed for tests @pytest.fixture diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 7bc2fb93..069d782c 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -1,7 +1,5 @@ import pytest -import json import logging -from unittest.mock import MagicMock, patch from netsecgame.utils.utils import ( get_str_hash, state_as_ordered_string, @@ -16,7 +14,6 @@ from netsecgame.game_components import ( GameState, Observation, - Action, ActionType, IP, Network, From 2cf578dddbd3d1b7b9734fe56027c9a6d7cd38ee Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 17:12:44 +0100 Subject: [PATCH 110/112] Fix tests --- tests/game/test_agent_server.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/game/test_agent_server.py b/tests/game/test_agent_server.py index 67f57d0a..233e3d3d 100644 --- a/tests/game/test_agent_server.py +++ b/tests/game/test_agent_server.py @@ -16,7 +16,8 @@ def mock_writer(): writer.get_extra_info = MagicMock(return_value=('127.0.0.1', 12345)) # ✅ Sync method writer.write = MagicMock() # ✅ Sync method writer.drain = AsyncMock() # ✅ Async method - writer.close = AsyncMock() # ✅ Async method + writer.close = MagicMock() # ✅ Sync method, wait_closed is separate + writer.wait_closed = AsyncMock() # ✅ Async method return writer @pytest.fixture @@ -54,7 +55,8 @@ def _make(ip: str, port: int): writer.get_extra_info = MagicMock(return_value=(ip, port)) # get_extra_info is sync writer.write = MagicMock() # write is sync writer.drain = AsyncMock() # drain is async - writer.close = AsyncMock() # close is async + writer.close = MagicMock() # close is sync + writer.wait_closed = AsyncMock() # wait_closed is async return writer return _make @@ -268,7 +270,9 @@ async def test_answer_queue_response_is_sent_to_agent(agent_server, mock_writer) async def test_cancelled_error_cleanup(agent_server, mock_writer): peername = ('127.0.0.1', 12345) mock_writer.get_extra_info = MagicMock(return_value=peername) - mock_writer.close = AsyncMock() + # mock_writer comes with correct mocks from fixture, but let's ensure wait_closed is tracked + # close is already MagicMock from fixture update, but if we want to be explicit: + mock_writer.close = MagicMock() mock_writer.wait_closed = AsyncMock() reader = AsyncMock() reader.read = AsyncMock(side_effect=asyncio.CancelledError()) From a08a3b0eabb7db73f71736baa1406ae34bcd1cfe Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 17:12:56 +0100 Subject: [PATCH 111/112] Better logging of errors --- netsecgame/utils/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/netsecgame/utils/utils.py b/netsecgame/utils/utils.py index 98f3f772..e0190eee 100644 --- a/netsecgame/utils/utils.py +++ b/netsecgame/utils/utils.py @@ -127,7 +127,7 @@ def observation_to_str(observation: Observation) -> str: # No more escaped JSON strings inside the JSON. return json.dumps(observation_as_dict(observation)) except Exception as e: - print(f"Error in encoding observation '{observation}' to JSON string: {e}") + logging.getLogger(__name__).error(f"Error in encoding observation '{observation}' to JSON string: {e}") raise e def observation_from_dict(data: dict) -> Observation: @@ -157,7 +157,7 @@ def observation_from_dict(data: dict) -> Observation: info=data.get("info", {}) ) except Exception as e: - print(f"Error in creating Observation from dict: {e}") + logging.getLogger(__name__).error(f"Error in creating Observation from dict: {e}") raise e def observation_from_str(json_str: str) -> Observation: @@ -179,7 +179,7 @@ def observation_from_str(json_str: str) -> Observation: return observation_from_dict(data) except Exception as e: - print(f"Error in creating Observation from string: {e}") + logging.getLogger(__name__).error(f"Error in creating Observation from string: {e}") raise e def parse_log_content(log_content:str)->Optional[list]: @@ -192,10 +192,10 @@ def parse_log_content(log_content:str)->Optional[list]: logs.append({"source_host":ip, "action_type":action_type}) return logs except json.JSONDecodeError as e: - print(f"Error decoding JSON: {e}") + logging.getLogger(__name__).error(f"Error decoding JSON: {e}") return None except TypeError as e: - print(f"Error decoding JSON: {e}") + logging.getLogger(__name__).error(f"Error decoding JSON: {e}") return None def get_logging_level(debug_level): From 0b139efaa32173a3738b96fa5170e65c1e232bd3 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Sat, 24 Jan 2026 17:23:39 +0100 Subject: [PATCH 112/112] add exact version of pyserde to fix cyst error --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4062e5c5..1761c00a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,8 @@ server = [ "Faker>=23.2", "numpy>=1.26", "PyYAML>=6.0", - "requests>=2.32" + "requests>=2.32", + "pyserde==0.21.0" ] dev = [

~M+3jU@4B;#hk`l3}m?{bCF! z&(A$N47(D+Uej*Ay>HSB%_S>7WW&&mVclWPKtO8il~wj(YFadlo8JV{CRAJoU-C@P zxoKz^c`%_B`sRiXu(Z|?>{5>7jq`rP>?0{Tw7$B0Cw7$TN5oX7M`hNWy*qGmx?`R5ez^bk`g>}eJ7L3aJ&(#w) z0ZC>++27(%Zw8h_(ab%(#|ZG%n?KA2yrd&E>;GuM~sZu*OsjNgrp~T?c{>GVwlA&c^QO4%WiLrKkd=GCZI`j>zC_XzU#t@i$HbqESvCiI@Avxoz zV#29zFBC~Sg*O!)?+PSYb4u}_l?KF|71h?DYDNYAOllE}W$H3mBu94ICfKfkDS%0= zSARJ65R+h?o_}dE`*_pzp9MBGK>YLErz$n4S7QV~L+T}9x)lC#TI>s!KMs*apUa2M zfL07*dgHT?L;j!5IwJ|5LtUA`oQaX(Z%I$p)Qn48MlbEf0d8^jw%a{68_2V|#~!%# z9_41@pB&nv5Qsk3Sj0{`%E^iIeEXgeJJLvq+~SR0OXoH`CsL`=Tyw5gfN}OY(hRpZ z;Woyvm8TkeKwO6d?_|%E3({}$(o53h+`cn34y{m|AwDL-9y(VmKOn7{RHUV~&n;D# z`j?Ik$q$$BRM}joc+0SVYPk^^Mr+>n`mttiAi8QBQRfF&;1V|N-{b5WD8jJFuXnk5+CG!j*MTT>A zr6$~DWcl1M$;KUkM9p_Tn{VlhGS|e~Nv?Lh(|$t<9$5;%tK;KI%OdCz6A`w!Avrxf`*l zNmrDp&_1jIPjw!V_US$6OFR5Y?I<1pNxnPW1X*4J3&oHtXQ&I9phz zr2FPo&+BTkUv<0=-ZUK#B7ZN2|zKAj^!`7!qDU$aauad3Z|C-p}E6(L}ip3 z;p;e7eUqpIm#WoA^F0=+xS^W!SPI?A_5mi-S3#J3J}A2hm&_Ii4Z+fIf|>>sfX!~K zQKZ`e_`Qnlq5Y-Wk{GxzsC}ko1wN3bH?Jp~q}Gv=5ph9Aw}39^1y+Yf^*pK}{o9CX z8SVINVI{@E3ijKHPg5nkM;ag$W?v=!bZW!uy_BQS!~WGvM-zjbriO!W0aae%ZzppzL=df&^@IFNsyer4dd69)8`8y5B{LgCt3`-+x>dz<7EvGYJ z2rh*f))=i_j%5Z@`YZLA+Hw{)YKeVBtPc1qPBW-6&$Z9UwhD=JcJN zolU~2;v9M4CQMm;kPoH^x>#CJrc1Q6#jEss>*Qg#$3#K;>8m>gDQ*7eWyWQwPQ#T)oZ|}1yNw*nEeIj<8I?eUU9}Pw=Qna23WOr5`i?o4mCXrUgaxB?|vgq!i{(pC8Quh+(8=TegI)~ZbB11a4@m>z(=rk zthF|q)`Lz7T0Y2xQgLSCfnzd=hL59ybC3c=;}>gyZ)KpjAb8a>U31Pjem=;Pirg#J zF^4cBnh7$%_@J3$3^rESSY0n)eG-NJVbwT;6h}%+rXE~f#atfAZ!6e}f-b$t*V;hM zCu{wrlYezCaBQH5nVnWf;_vIx;h-5}U~%u8Ybhe)8_ta2i!BKbhd>SDaM2SDik^rh zAt1!43Vp=ZH*zt|8%?>xlSEL8@*RCDU7E&af$~&GAYj;@Q&=a80UNw$OIBIs@0HdmYn1IA`}_rTE6tW!iT`!k0t&`$lj$bhd)q=L z5-7FOooc;O8S@3`0UIk6nyS2rB?1u9h`YgpOhwEF*aI%v)hTV`=Jpv%=9wyMx~adU z<*jYxkmZXV{%4sLDo6+K0c93G(V|80W??=IYlpA>VbOBzk!{|DD`}yt5gw&Dg$1NP zOTB4H5cwO`J~)o@1Y^YX&X}(SFE$Tb708?q7T^>P*1#s-1?d+oc7B$=9v@E-O&vIO}ez` z<6x+nE>SySDSL{C@yf;qn9ktiwoN_KFX=I%xH$hRtRctPlm{3~kaI#gJ9WW!R*cu= zi9h`DaOfNkb22T&TEs8kXs7&W0ShbG^lS1Ma5&fiN9~b!3ji|F+4H86%jThe)QO6H z9j+A`95ouOT}G(qW|ECTE(h->UIu8LOT#WrPnT5RW$>?DiV9OqkVf!e4ze&QUT3_H z5G;^?_Ck$cA1Q$LRdJM$OoBp>Lgy6k3A1x*caZ>YeDc4m4aFihw^7?7X`-gnHORu z@_HY@#t%#Jp5a3>o{JA%_0V^(x8R$Pmzk%tn79CBMe3xNm*VU^@>0Fe>w#=s>x2G^ zeg4qooTIIu?`+d6H7WP<*;b}ffK&_fi)B#Fv!nZ>=#My$j!0=KS$M zMC9tSG&{lel>Oh-%Xc$^IWOLnv;JwP;op?f8Atlb(UlXtsqb|c-KyaNi<51Q*My|G z{yXv|V3Qp&0}77mUx!>9nSQj?Giu{KhO55OPBc4TgCK9k%<<_STl$qh>5_t|tp%qH z$u%Dq%ZIC^W^n9^UloS$?%@K}`1(1dKvheSXxb6e+)U1o87zT8B|u>R){Q8jljCl8 zC2LO6IZfN{)F3z-yU=g}rt#Z}i}<5Uvyr>?+WOzHtiZ_w&ey=!-Y*v3S&hx2TMdW6 z!Z=3{m>ks90Z7h~-QEw@_%t~-LaG{a`5-Vl#NvQ_^3=FDn~|VbAC8NkeSQ^(_ZGEe ztrWy=?99EF7Mg^`BbDP4g|{=U((+4vU7TF~9(zVWrifW|wB@1A?BpZ7mz|#e7&}b0 z6^QWF>js+fk6s&ywE4^^sIm;!Ow@9PYsXLELmP+*D%V@0x+?IGi3o^wW{ekEncu2k z&~9h4?aQ>gELL5tnJQlckwcF6H~sUX+M|n?Uk-jIJAR1Ur~Gz;eXVVI|D2^J@|R<9aQ9tLE2*dA>KKlET!X1?6xhUr9qB{mAXWRu~htH-A=d13Y=?daND~9iNv%m zP_G+;A{IZhYXW z-9L&&OfaI$iO~@sT;s-U_bq`WDf?8gyG*nCYWKjR$-!2p6&<*Lt0knUj%F3a3S99I zK_l(ZFu;MuSBZ|hmhPzEOE#=HdxrsX zaaruPiK;p4tlegKp-@>(^5zvqTs8Z9AawP;9gE`<6M)pc*`;TZ+EN`NqjWHqNTJoY z-#4yPT@v<-eY45O>ZqTY$cY0epo4><=KwYx7l@fI?$S+mUJZmz3*q|cU9&XR8GQk zi*L*K>>TKMAGs1dGeMO8-M2_B7c060IU9u;--r?PPg=twCKWIH&D$T8D~= zfp9Um(k{NPROli0MyACunwyeiC(8&Uk4(@UCOKSfwhq;{KR^C)n z7+vTUt%*TR(mbLGklBK$={bGVzN?(oHK_d4NZFlnGnLKKUxds|sxDhNlPxMQq{;Wv z$l!p4gPCVPWrz5M9MAQzt0w1W((twS*wG~ErKf)1C}NIoQ6}d0xNh-#RYD=;6ww6A{??~3Iy%_?Vm5(#E8t*Oz;O)JvA*`^)Ae~ z`8hvQKzCax0XhUvE%vDj!B*)OxQnu7rspl!fuE_AYZl`g*?a{Ftvr7d-Pa8I(7k*o ziK)nkTJdWXmR|ugu+S%iSXsv(L-l?=VViv12Pk|BA;;c?E6idmSWBD^ytY%+|F1BdC={6#ezTe9-?-Pq5?uL(cA@_#aDB z{~Wyb^C{B5fkpq<7fO6C^`qq`OHFH;$+>Y*G|&!4R#|JUzqX4fGn2mGO=u=OVvB@v) zaX?|3S!<5VG2kfm9Xr2u)%R`O_D{tAxRKV3WnQfqG~P%%1BIx2a^ljH2hQatGPeV7 z>6iU&@bo>eW*_JKodNnp!c9`_(lkuMdK+9V&5X`vB&*j^pbqTbD&_nXMsEQQKBzNY z_5o)$`Ch1E?OJnl443Zq?s^>|wd;&>u8GU;yl$4*?r^)kb|XP9ey4&Il&MiMZ%Np` zaRlxCnh#R*J-NsDJ$1sHg~l#WwXtW}Bxw8va4%vlX=DYum|w z(@<$W78EzbwUiC;kfFc~c)r}3;TPQS@eRYB+g6jS-8H3#VjzMw6i`!kLynfM%xZf? z20g_B+t5R^zL)RG2kyb7Wq%Mj3leODpH|HZtkrSNL_>eM5GdAIqgZRk>X-BQe z%}YS)#9fQKGj|t9nxDlU-nJMS*3B{SYC>PFnlJ`5=LN3|lhyC$NR~@$d`et4tJn6Z z)+Qvqo=MV&-cw_U*o6-}pDO*!s)F0if87sym8L!Al0e@!SRBxh0WHDetG$j#7o5f` z633F&r{6Wk8Q2>h?E{2IZ=!@;B*rxTK7C}bmt_Ndm0MD&>rZVb1hVnhUGHeA=gAL2 z#>6Fg1vWQBm!+g>_vy+`aaWbjYTjsi{=M`?Dh+Ia6B&+nxZNiOVrC7C_FV=+XUP^c z5sXyr6GeS!EWb^fDfrBjq64%mr(N-BMu;G3{`Xmg)PAGC(7_;C+AK1sgJl|Srx+>A zxbrZxF}|Sk3C}kU4W~s=l0T%oN+70!J!GlpS5i@fU%nv}bUv_f^i)_mZ+a~;XSwDV z1Tc_Jggt)w2Ze~i+=}xeZRmuv@5t`R-VPDjVB|`zzg+^g-6!S7CH*6EX zvb3Ev<;()MlJP)mjk?tssak~0Ylh|ol0%!l5Lm_5JY(t(KJz?O%!5X`X^$bas^PIa zng)eK&Och8)*gQRG#qnH z0LL9^ex)p6L;29qPy*%mdkpOS)%eAb)5=2A%ha`&Cc59kNP8+jPNgV8M{CHYKlNQL z5%^wLZ9>$F)D|+6n&J^-GshF%V;E=fMn=hlVd>?gvAM|_?>bUyTpxhlLtxeq0u5kP zn2S3UVyx;-Zs>nk+E1qt95i!m44gU+SBV z3Ee70XaS50mFq)b#_=QlGgOp~`dEA$Ru*So4eIGpVT-oX&XhOf8_stmz%3GXEfRV` zZM(r;$6w9%m~D^l`!y=?_*HEc$w(&J{&BQzss_I-wyWC|EKdPFq18_TMXsS2DU@WP zt@GXTgSg?=Q;{bAn?HOU^;&1kYo}=2({`vb*#Cq_hF&~aEY%D0PfNDKdRsH1qyhb z&Ou#>e0x0NRCQX}60bxKT7eCGC6_$T^Ebj&8YP`yvJ0L%32NZ1pl-*9Zn|)#cSg67 zL>mfr>3FML<4G&__O-+uQ$S*3r{~_!@xF09*cbI*Qe2&stX5?;Z@t7Kh`co zhm1}3wQ6A|8-;3St;O6|#@DgUQh^ZT>{@+uZ0vmHle_LVvl?gAb4_B_)`?mb2S+>a z)zJZllN%*22V=V{;_JW{@JbqK_7`M%0rkWw1)w-MSvuuwJfiCIGT%HE<$KPLuX=z7 z>=*F@KrHRZg91GKR{N=QvB2y&0$> z1t0P>sk|_0%_=c)qh!~Tb|6?fT3U=%>% z^br$yI1NtJDDn06xLGtmI-oUH=rV{q^d-#wlO}@b!ubz@1QDDkTQeTgP~ZYunc`z9 zVL$WbB<2ia?ZS8DZzqK@XJDN`o3t%AWj?2UET_7}11jSg8?|eOl0xyJ_-{B8I3u8i zfvh9ePM6`et+W;CUJIvRqQ2jpc-0WSs4%c0P4vz;g{bdHx20}7?bP=R_Ak#h7DX88 z{Q14(1(_ z+6=xoQbv~tDGUG^NN(ZW@Ls$3S*8mNcx&@}7xuvyBjISB(+OQORH$RhivhN5Rn~KS zAmFVU$F`kCfliGJd)PF`ToZ7N2XF!8Tw+Lw;O9WK9<*|jd&Uoe#0US)S@ll~l2`3I zAE1@YW+%0k{R1SYhg2V)Q_~BLCzdztkH53na_o}~j%B|~&CfFg$^FQf0nj5k3?PGK z`9WachHZ;RQPG4X%(_FlAbkZDJj+r0?Sxvq9{FIxb!5S7XanU}t<8Fhxx+#y@jUCw zqqpoIO@P3y9R@cldb;ndfZd+}w_TjjfEzJ1z1%IRnJ<$LErohJl|25z`yH_2DeulE+1ltAVkd)0oXHnx~y#H(OHzM zwY)J+$n{R6JQCA9`I9ZmL-BV;geN2Pc}vbK>>?T0HF@*FEKBY2qR zp>@t+dan7Busg@V2>AO2|Lugz+qba>M&ThX))0_^n&MrU zkatPCt?0JXYCwNmSUo?2wSks(iD{bo)7@o$Se21_w?&_$uC0CB`WJnv~_oE>7h}bhLRj?Z5uf{O|S(w5UbN)Vjy3Mwq32!@v6wLIl#;xqwL$ zf7z5B4|R+k0E5&mO7s?d-hpC8YoPC6iYhOfQky7Ax`uy(9NlHm1oBFa4Fbn8l6Z8vju2xr_G)OSTDx!Vk6ZO=;G})pfr%iYbs%}kz@=9jFT!Z*&T&gE#kvuYq0Rg-<;X3({slYs zjwI*~Q6j&mm;FOxX%gvqXr zwL#+G+0exILHFh+Ns?tcrXp^M+Db&!iMh=|n{>PRx6SDLIL3}yO|To!WEq0d(%kZ` zt11JECBo+4NeXrVZN$MLIxAR)HR@3vIO?uhjrgvgl&5CLSI!^&g!Sq*T%jyinIS?j0~%b909e)uGkx`pxia}}w!S`VvR zafnSD3nN4!bpRj}wXk{hOJ=f$>MjLM`F^;teJ7tmF7Z+T7D44e>Q;MD;hv}YuMJM% zjt+{yi2FrsU(Du!O?mITXGReDV9hNJ|M)rkUSj#2oXfV$hFHE&SuvnH&&O=7v3>}_ zAqC_B`Iovt1;LV&8~vh?p25s5{4Acb=wqBTeC8{Ls7wXOZtt?2BwQi*Io+7-o{jjF z$Un?O$)3+M=8(+dpv8$$**Y3Y@ zh$cO=9}B7Z7d)C=tBE{dnxv;!oakJYODdzjPmxoEx?tYdqnpY-@zr9aLa${{4>K8Hypb=b+(b#B}|dc!FCH9tm= z5kCjv3MLvV29CX-zEHIq#iVB*7=1T}3;SqL`_g%XR!swl+=SjzulF280}h1@B7&Qf zstkC3cYQg~&@UD)_m7}*?n;Ib@1bztm=&H^`ONHOhO^g9rbav-R2i6A@yDUMY5`}- zq+&d=rZkhH;H-1EQ~EIVYqRY+Uc5AyZ2cJpaqYH}h+b${o808yU%I|~t4pU2z2}IVMi=dxg?83b&mND9OgAt(>rW4); z_>u$_eI@4s7GjGukCh0(SR%F#2yX{S+7sUv&7N*_THDRFo=4u~?*#p&OEy{KcSq)a zKJ)0Coo(kU#xDxA*R(v|o1mOeYrpTQ!9nBeCy0baa9V4U!#Xf9WNk-ZwkF6>qxBz9 zc4y`$hhBIaf^*_jOwt8SBym5UA;AGdu(iECC5rpYucl`nbDbzB9|(0^ClBu5YAR|o ztxt|7ccf*J>d%<=T3r(Yltg|RmI%m*N|QCeXhF*SYhLoKoK;($Vge2Nq=`quC@4fu zfya`okNWLIwAli0{R*j#(aV_L;c(g*GPuID3X> zZxwRDjFF7DS&8(!DsaOAE?}RFaUO1V6?5KheICZ< zoA~yv_%02wg)C&d?_yQZ161(vMUYg2Vk58(D8T9>kjWnz%sr}tpD3XH^2nYjR!Qj8_PGhxye z$H3II=3Ky29+tHx@Bl;X4in4Q$_kaL@5~TBM)$7g53?m<2gNdOr>%_AZCG9rBSN6Y zqUcVpqwmP%9W{GLrl$?hEr834P= zZ46halIaxQQ3|HLx%OZwD7Mu&cA<%V&Q1P@DCDqVB{c|o=~>)ug&mueRxt}bXS3(S zu=}jg2e^cU*EqFqKe?3xV@d8oU~=fC=OLu>jb;~!T>8Ziy+QrZoyD@Xgc_F3Oi=}7 zW|G-?{wt(V;|?`t>&b_-*&+w9aq|29<%H0+7-B%`qFDWOZt=uRs3OGb-AL!e%W%?! zCsmkZ$d2s2;NrU7*ne^C?&q5hBFVAQY8aSr>QW%7UsQB0t>uFTNm8GzrE$QTuU%f5 zjz!)c^7SR^6`0$1-(BwXBXtid2RkKgZi+H5Zl|j>zQ9+r!JMie_`ixKD^S+3*F7|I zZOH<5|Iu>RvI`JD<9qPcb*{@%r<3^Gl+c`3>Gx+Ojklv1sWT zP?&~>IYjUITxg1S;>~P7C~R&I`WT ziV%#W=Frg7;0@u&%hIvF3351Y&&y)_603|+e1}Cdfs@EDydz%Y&Nes zUtaeCR+0WW@aw<8LDGUv5UX7)2m9(^!ImTeJS8+6!m=pNC48Q>~oP#aMJxeJZgSm8- zSSc$rLF>2yml*+RLZ>L9_zE!NRUX{tc>D4*y5XIUX?^@%CVnQ8LG-#pP{!-!176Tp z7dL04#$OFj7N`aa56_QSVuWzIrQ(psozAYRT4Gy$4r41Md(|0mWc{QG-+z!qY@NTi z#MDAL#$0YJEX70ZK4X|d9Na!+k>_rT&>S4ZHeKr|#~^oA)MVz3f-G;%-Qxxh9FsaI@QjD6nJJ zV%GY8JE0Xmy~G}xF@$!u%v*m&|7EH|{o+8VD|Rg^tb4L&$vi=07yJ;3!*1`7(KV^D zUay%Yx!@QPE^eJz3-FcFlFvtl<*LHRl;<1RsW1)q9^SfSX8Ob4AkLf$eZTCx3|6fp zX=-N``T%-|82ztmL&wr6nK5qsNaKyD2xrf^yj+iQx^E&8TLn~4DHAo%w+U<>I^@7S zVkTt7JVNl$(@d?Fjx>b!Q;c2WA>874=c}lZPG|rtfE8GDvRu3kx@ICI0rGH+B*M+@ z(z64%2kqwVqcejegOe&99`qlHq1*dAXu%XIMFFC=4>YMY%OvHg!;wjkRRjCS5C2T- zWEtHE{7z`X1H1#!QH)t@64%?t_7*}DXx0a_r}AtW4f!e$gC1fYzIv|{&fRuuqQ07Y=CU@CuPFQ3;HRHR2qEK(up@)DPP~y1P~KhqsR|i z)rQwMK#Qc5)HJBWj<}O%`$_V36sxNW(qs75!Z1x4?N7>=Kdo0lBT(e~>T?##<~+8l z0?G(cqulPkU3bSz3&e?5O>AgZPAJjR!8u26dzEhNv3r{qNl9$mIbAefq5aW-3y%eo zEY1+hAKieZ=Z}BM3TFCF9^9DoU#Vk=S#|*FJBt7$13e0^E_6e2vMG1NkIjOH>O#vC zbQ{Bb03>2jt_N2S^8z5(^`}bX00vG62=>!1z+?RtEm6^lK%=yjAiS8XxP-OmsP0&p zyv!{*1)|mnz^=aYyXZAdS29g4y$_FDY*Iih@_YyQ;;^=PEFfw&8GyRkUx11vT_@%U$*KihqY zh>(61s-8WC8*Nn-VjcScG9rm&u#N+Woo)Li5RD?+rxJnE)tuSgBlw{m9nZT__A-*x=a*rt&lc8k>?KNZ{bL^Pg*$RcdP$P#HR_x^Ka<;T$y_ah0f z_lfq(8-|r@M`m;6l2`$l?iI&mt;R6p^&2WXTzKl6UmY2_PWs*xspZ>ogsnu;_}(D^ zVVFlSXZ4t0S#T^G**QG)OIo*ynNK2MQA;!)S?A0Xk>^ zQEOu)FBSkHdbrXxn(Mpx7Y+1ChF=c_GOLj2)6~B;nYDxDR8W7eNEl$5OV>)1c^*5W zVdg_*C8w$-YfmG=aWUeZ^&iQU2i^GrI}vtXjbS(Jk4zO!Ck=!-))nqw6*mWo2{;Mn~x{HRp3~pnL&rZg*dRUfB*Y$t^>j{0dG7C|_?T zkN568JNV{R0F@UYy{M@jU{RAfN_&tt9qcyvjZ|&%@d|#Uc@yP1+)y5Ts$%?3!^NXk zGD&xtuil!o7qdS?tbCAmnfuZWK`C03Y7D*`%?NLoB!uAGmaFW8T2$@M$VGH7>=K=Yb+bL9-#F3E}>Y(Wr5nuQ%X_N7fYcjBOV)d{&nWRFQ+C^CO)c z(ed`{-q*vC6gVv*GDrY~QFS;#!5*AJh&%vpvfci5jXM&CNpZw&KyPSe*uC*_n;p1t zn+7#^32lU8U zuS&rt5G7<5QPVK`_eF$^m&RJ@X~#PnJ$se5-y4BQp*gVx=+xbK$^-)w;o`bwW89!0 zxC;Y7H>+G^@X{67^XO=kaBF&lA2o~--CEBBL7{Hp1KjX|)=Vv+a}kh_U%PZ+;N9{s z9;qXcD7mLgRhGf5?x>htru9!kd#%I>)zK=B24gnRXU=?G7QI48mlH=ZD!G2n=fs+-+IqkzXUqF#4-8pgyZxgzQ?9rFY3NKZ!Jcf zawdGd0sN8<_IplRB*4(?YkXgqm^H{5$G7Av>4*?x?joXY4_y;W!G2l&uc{5lVNR8W zw#`e#oa*Ni_r5w3Ca}UGS|V181|qUXX~94G%|X*!iPB_SAo zqrTZZOWmP6(Y8)@nC=_y@83B3e4sC9Z#SU5enUJulQuHZ7BzFeI^v=!NBrXX!W`U! zF8v?^Cj9YaK)}p0zCN01&Q@1Loxh5zpwzHT!jv=4L*?ZJHG(_IQO)b9m-S{ z0f?-D0|=fhai6x0KB?PJdxi4tgOr;BGw=#Yto0`@i9_DxdXnn0b135l@I@>F%d|lS zFYL9;*7WcDz!DJx#rdFVcPHQ8jHC8gnEj*B^23HiRmZqxr1-39yj#hgF2~E`UxzxT zxW9T;?-JW9-!iOE9WKJh^W)s%mjy^7DY%&34eATHGqbqWYcH+_6pq`4pzZv8fnDd4 z#@~~MpH1Wf!wefs@V}pM*Aq2C+@36}WOga9)AgOH%vPs!LK+_)H|zax++&ko%W_P_ z?Qajx)Ew)F9sa!Y{m{!Q=+2WM|C@t1*3!S7eYEw?#7{p$&Hl8KCllRcXvi!<@=02y zdLdONf-B;cl_D<1}T)T?4~^ROc)FA_RSILtgLNzxwIOFS+!43 zt8<3qBrwqT+`d;>*&M?E_ehC^Th#zG6$Ef#ZEh3~op$h_o^5P5s;rnCZfPn<{Out{!!~ z>RpjG+DZEz-*83pIf8_`!y_-v3EX}nm&+}SsD7Vom+fL?VKteTW6XCh8Ld>Pw$^+* zX6kBiX|d8L#xQUv?BQ(L{_C~4;GXjRbDMCXF)ebpx8nuI>QQx_%lOb*bZySc?49g6 zrPha|&^Q+tHaFSQC7x>%C!`z05S+SqS`Q*#PDdtbe*`)kXN_E%E+Bk5H9K1_5^eKH z@kVMEnFu^OAK$)XN}g=5c+J}Yg*Hs(?9&_$8(t45PQ5zpx7b=z5d`0h&ZZ+INq0aV z&PF0Af7uz+RI1HkJ2+o0aKDh-W`+<JmM`Ltr zG_gq#V^E*fnw+uiacYwiRa^UaLT;eF>TW3JZ>k=GW}R5-T;AD&cC|64_PlMBqL4fT z5K(397t2)N(tvBZnhxaSSS@C2XW0zxC+qwNEQaFHEJohjt z5#vdjzija?XE(sYeD+S+hubf|_L%<9+TW)~XN;aBf<+3)#Y;c6++Ha!-+f^J)QVg- zWe;gwNf>&RYRlu3p-U)#H=5iY`gY~EriLs6~|u=Fk*}@zUKKIoDW*@v7~K?LGcrmalau55i)8PK9=3flrx3F<-oR0azfj^jN{nZ&Tg-#xAcJm|GT)7ib?t3(0ytV?9I4oYpFe zP9e~|Fr7)ixZR<;De;eq+=vOiY;rfq-P1F^sHhN$B^J2L!($yBxc6y~|H{ehcD*6~ z8_8sH=$q%LpC?AmZWxz_W#O5D=RzACU0;&J4Ghv#jCFOjZMUvP;HWY#PfXtRtshcnL)0K8>7>Pq{aA1~@B(vdo9ts?>VOWZ2 zw4guUU_%B0bQkJ&ql8f)t!h3kHe~aI$cVqa_)>~<4)L#&^o8w@kMGVwe_y7os{xiN zr4-Os-*T6$IvOYs@EVy1x6jw-yLr7KcDHkVkc6Xzu*y#qAc|0r-MUA!tC$Cvh@Zx- zhFW51fai*DLI$RqTkZ_wkn*I~Ow2YYoTI4`{ zNy($}kGGMd8tOBLlB;>?mDLBcv1&QV=5jpyweXjpCD>i&zdIcTYuy#HXn-<~DxusP zBLZpMg7L*ey!Thn>u*j?b+CHZnuEC>xg4Dp-kF}xtJ$pNc8jb6#=g;SC!7aMY?XH- zUg7JSXlY;aGV6Z$0U};DS=i%m-Owv1eTb`VGT|?ifJU3K%`Y0{+$!-#C4iDxvVR7l z)Xk?wl_mmI;J8p6E@+I+Z!|#MsX9hO&2@nk(Au1;iqI?qMA#QRlj^^3W+9d~1f-(@ zRhFo2C|wC~77r<>rwzL}`TPuL!4r%@&46lQcSewYsDF1!w7@tN16bA8!WG3cHLxod z@E+T+kR=<_@hI}XT5f5uS%>AlKu$X?ettYnf-ayZx9`}wB^>7$SRRl^Duqq2w1Hb7=n2xuy&ZcaEWR{w;(AmJVSxpyeiEJEcf zKvc>^S75BRav|{(W4Z+%JDAuF5|$CY^&SxX?&Wwo7o@5sruMQlC$$&58M$>Li1B@r zXgx14ExntNS~A3K4k0&n_hCFh*{qLtXByG|eZsO!<{71EL@0~M=fBCZe_H6V)jGK) z?`=g^T8-nx(`V4M@*vz-U64QxYo8(sFa43u{q$cj-afRQjQ5jcD65{gV894GVpu9h8hP9s}S?h?Cit*RysP@1A?3YoLUY5Gcj4y`YjuO|KLDasWNl8 zWk7cdN>Rj=!Yka4ZX2(Vr%#l)U-9D6BLWxHwBnd8!=jwHa9_AnZMIARRr9Wy6r0@Q z?Nuc6y$r=DDp5Dg;VHm;)6w%s~hQ2$R;T3=$4iWKJqb0to~mgb+eVl~JY$A%qzLnMs%l zv+wSCuWiq1&-wh`d+vQdzk6Q!hwNbu``K$hYprMaehoP#^DZ?=*=>{u1FW#7n_CpJ z95Kcc=N$yNM*oDKr8VcLL)%L5IO`j`@M!B@x9HPpgA7aU{iV9aiwD^E_6MUP1O%6* zAiU(6RP&<>hb0pWx^#&eLwKc!5HLtoRN)8tZ*2`1yBMq53!Uc%Q8$F~oYAOFReYJv z$D7A3L*=MYcrg(ZZ`S@`RY&4V9mjr@+`84EK0O!UQ=r^*c|T=1H@b*vuz`7j#KPf| zOe-1_l^>Z9%1liyb}!rs=RzgyUqHFue)`R7v+h^QG zm#z)KM6LWiT4XIYwe>rtHsgUj-i8{Sahzdvj*|xG`3UEMWJwBDaJ&e*%Fpp{rh;Vm z9CV?pZO4A&AF->4<0|w3Ez}52m@B0M*2{8q!g&CvwQLX+Vj8jWbLB)ZGM6*-~#O; z9{BQ>lN+}RjrXbwKr;PtELMUUIp5>!{gyt7fF%U_nOyDxXHnQ@0hC9s5iAc19i#0O!(|Le0;0X?jryV zOCO)QTNxpDf$dJR^XQfe4aB_bhP%(4NY zeZ9&(JC}u^*%xM?tWzhJj$Bw-S)mT?Oy9O>($`77!gmg~6n#bYSR;09)z;HmY^%n; z2e36_{RdofIsRiLI&7bOHmTwUFa6ZgsFTKrN z$)9RoK~K0zKXAReS-$D4zmkHReuVco?rpj9>?v9)1YK`Vk>jh5>)Uvy?26ru@$ns= zFmYi#nTy)ul{KdpL%9rbYp-ezTYK^Xs*$bl`Zsf;*yS~&nt{?e7q@23*#Yr^t`^z@ zL~b@c)DZ#$Ia)OO*dnJ)nU$MwL}vRZakb2(eTFtLZ9x)Fi;9X{3zwo9kAmW-+DPSM zMjc&@m4?5YbH!PNBNAJIO(Vj`fqVMALZ^zQY3|EhDIPu8FDtF>vV7!q-!jjlqq$C7I%FO4Q6$hb zDk_q(*kK<=a09lfL+xdHs!0d4MPYyTwTORDU?vg-h9Cq?YnzT<-E(%OgCq`lm>zIe z5bj`$q$85)I|EqZz52W!12^Rq9gg$IkMqZ9NgbLaR3nZy(`;76*Yab->1;u6^>Vdm zJ2kAB7C&uQga)`h<}LMRXSTWJGA1THb2!^zg4k%P^o-WIb0)DJF8PvjDelQ@Ht3Z= z(;t?jb7PDZoF<%Jjh=SdkA7J-s4>X}4SY;9Xuy#>VwwEFJr3Tqeq2rtWoUQR}% zQO_~4jOYjiO6$-~yket_DjnL<^V(iAUCzN=^i(+R;6x0t1~S2|K3TuoldxsF^hP9c zHKA>QF+D%~(CYAt6HC{0xgGIdjBZd`+KmI$qYe=6CbrXEX>!;+zliduy>bDxr#fhC zMUtfF3%MB#f}+r;s-N+-_N8W1_= za8cf8#xHo$d^vI_{#f}T&6$Ri`og89(7llCHLl)BlFY79SaYD?u|1#eO8CHRW8vF_ zFrlTnWaDx|f1z)z%fvN@z>`gqXYo2<>%Y!03Y!Vy7uc6LS_^K~lBp@MS}ot# z?#(pli!8hvKx3o~SM<#^$oQ)P3JeV_{P)3?a(a~JLT z1Gqh>rTadH>HE9f&KLbld~KdW&;1?u5axqMtX@Da>)U|z;yKr>{_fMFX{Z*@DZPmt zX7Hdt>6!couV)XV_+1!jn3SS-F8Wb~-u4=wPzx-K@3&C^yorhy^N1L+U6k(|5s8Q$ z{Tn3ctCl~lr`{%CAD;?16?sAH+DHSLxX6XIQ=J#!K+cG_qPMX>X07Wux1Xi23BlJ^ zF5Z^8Mbe;s|9p5T7;Y4vn8o|<;Gy}RS(Fn@*hNaJ`Rh9qu+*$wu|g1!QkphtV*p)n>2l&0_k?%I9ej`w8FhE~sJWil;r>$pK3nWM^Glb{i)j z$B`* zdgX5ZuMHID+krA(3tv&X8tQys2hkSFRwhg^%3-w zKiak)+Kl?e`1vFG2x&Owz%U|r`nKG@NL}4nMnCf3N?b6 z78&x2lTeaNni`{e>gGC_)z&H}&(yND%JA5|)fvE@keWh?Z?@EirT_q+Ir!`xwWP$X zr54{iuaB^Yb~KfTUWSh_2pe?^MdJg{EBdezk;aae;%hhswX5rY+u=ia{g&v~*_$8C zJ^4RsTalceP&e$=QdZ>n7J{`}{UCsxWIa80leGUB!^&T3x+B^6xUJ;Y!qNh67p*2HbB1AmB$p5UGch`QEz!B9Z%`?Fm z(I8W=M0du8TxnFgH=@(jFL`Mg+m7-}I~KtOz`iiC9?wL=_vUox&NP%4m;Ib}@JK*` zm7)P6)vH(SjS#Pq2j?I|mhx{z+=_RaEf|{TLm^=+gv6W{Q5tMCI;U)om?F;8b#JPW zd$j02Gm1}+wq}`3$G{<(UtsTZTI7|Q`6H*~IB>pkuV*{Hz6}|6Pz&t#?a_eMy--wa zWBF*{G-~}CMrtt)TsBUylp#b+atu&{p)5LKjvGeI0r*lFbp^F`j!mMQ?U=p9WN%fx zqN1_lXFI<>?EmLq@70BzxPA<|?9?ascf@^WDRjY7l!%MR{XeSg&~Mjdl|#QSwMtBX zYp1Jzr9yFP-4dRd@wf2V+gBKq`Ee1_U4sD@x+!oG8~So{{a<2G%#vn4|1nR(I;^h} z!RcGt(BzJ&9?Xwd-rrxdGBD4+RZ}GZ&ACvWZx;_0+H=+L%HDmJ3X3FClLTZTdTTCC z+8hLMXAfDQ9C*41Gl&fuB**Xh%Yj%561dqrwIHFFbzJ@fZ((@(;iE6xM~x6JM87HX zA5?Meogj+Xk;}A z@Jq74af>C@$-_M-RM%L?(l9C`N;!FVO?G$P-pIG%lhiTB#c#&Dw);@_#V&e^ zzTb?!dx>)y*~DY=;qLIzmvo`s=s=FL)8vRzvt~Aayjyp)wUsUov5hLk#rrY=+5*$R zG#_X#85uliI@iuTzX%1F1%AUwkRQ{64NpfqbeSM+Ap?^SoqWNf)U4h1zUP7owcLkL zZM_N8ZZme|QI}dEi)er8890|tb*CWpwXaZSl4n@3Hl-4%*CYR#Z7;~Lmaes$Lse^f z`X6kfc)fn}*T?;xVpr@3M~Frn)~|E$=1Q|GxBCmD_AK+)bU*j!J+rDaik0|y6KigE zLckZ+>W=zUKil8uvs0H3(2s^Hj`+@a&*joAdMV_l(jc(4^#1&`GP(+a$|goz2dySz z*8=qfQ$s_H%N6Cpae*hGoJ#)%w-=2}0D;fCL0&;Na$F&!!{6~7Lmyv8&8Zsgi9hk= z4s5EPust@HR&VsBAphd=4gJs?S`K(u08vUzQZ*Tv#xUxh@wbNT z4hCj7ilza0cNi~hRrjor&vpJLX|JkVY}<9<*}*AtN$A1Kb*KXKa->Jw=xX7vQx<}xTC;_xLZgAZ?kC^b5>w2?5W{KG)t8Z0vGu2q? z|#u`9tj`aw2PS9{)rM`UU zpd_Lj>JpWGS_l9c>#O0D&1*+&N*d#p7wg4?OH(<_4aTWcKnM!W5^5dMN>-{IAsPd* zy=S+4AEGMt@7LM&8E_hz&au9(bg*0MEcITBSYNKB>PB^$wdUUH($@5KLe^+<@RpgO z3+l7XVMfXdJ*~pR?v02>*J^ikc5c{yClZZl8oov7^HREqepLMBDvT9ff2CGI_LDBLg3cSiGv8|H8a-tPAW~O z$EJ!z6ZfvmPrVTV_MfznHzJH$>FYNn>A#b+1r zNdVuTv+Z9ql)PiVS@B0#XOO=g>|KZ1NSPnsizvJ1J&NZOCtJY4L~vkTwflSpq|*hY zYZ%vthx-iH>9;gaq@8$L!l_!^t7+`j847Ld%%xg%N}aP_QN@-vc8?}awP!}J)pdc# zis$lRy_<3Rq`YGx0ErC)=0D%G@bi#wXR=Aq(9Sc_1$mxBQYbR+IZO%C+{q>TcIOrk zQNq;MiWuPl{9(P9I}8eClT7Ohad$;o*-ex+s@=JOq2_bkQ*O!`uw>Gd34UXzK$U7aOS@IL!I9{ON<s)$ldwLibhV~7Xkxy?aer%cE)7qjb&o~3j>euG$k`{(0a^i^ zqVKJObK=*=&hnj*Y+LDhgkQ5C{I`YlNVq6!0=Oyhio*!d6|{9C3DKXca$c5W99K$ zN3{T!LRL$oXlvW9*xEN6jJDb~S{q^=KHhJ(%ihx%M^xOdUf=(!`4v}ldVqqnlj|pU zeUL;}NWg0Vnc_v*quZc+-|cA6 z)eUyjT?o|%!n>!yAOJ<sPx z*ciZpTz!?0vENqfbS(EkkvD}E$~4Eitwmj~tMHrYqd%T*48tG~n#&I>7{ojXIhqh$ z+Rk$Iae2;geg>XbPIyLjYt9dJ?m>p?{JN^80}KQ; zIax4P;VI2`LRdWzD{M(m_Z5>DIDrR)2Pl@#n$C`^0NW0b2ep1tl=cX&SIF4tym)*O z*7Bma5+Fe|<~l$J@!4s2m8D2{oK5i26JK5 z>_paRN`|G*ijijHh21Qh2u~(cU#Caq-la+Y!A8)%Bm2pEk&Q0q+BWk`FopShzJWO> zG?B$~0|ph#JOEW>&r9L9vmfg@I2Sd%hA%sMG&8-Zzi2kqFLBe5Kaal;%#oki3ZG7% z-1HpnTYT*t;NQjxDtnw1=X>VdqB)0q8|G z%l7GlzVr=>);QO(qn`mS$;No>P(Y*0k-0dK1c1V2fPLYkFISE7d_MUhf9N!7zOZYs zX!jA^f2xlH$a}a2V??zM(AOcLb84MUzlSB#(LC8kdncA&LvRo&ddT-uCk3RpdOlRZ zX>Wqo!p)o3xx&Y3GCFq3%eX5;nN%Bd$J`LhjCL<$g|V8(S3syEXmI7Qu#VMO9QCR8dKne4v=V2Y!VU9iXiL@?`4 zQK0leW_n6m4TxZD&p`PcP<+dDI$QZO?RoYInE(zB+S|c3b++6ld};1^-J-b`{a$i~ zGp%~12xJnAG}nr8uKkVO)*aGN=)ehE-#MVEHMMIm18^8~79)Sw?2akC+nS@22e}Ou zTW%VhfSj~r=MW0S^P1s#!2C5+#bWrk1oRgtQ}TdM6~yLp(IxnD38p3>`i2 zDx?riMPn)*yKcMbEN@n|l~4G}E@MZ&@fqgiik8EDQ#BITqbI_c8$Xo_MRt+&eYdJ? zH=AIy+1S`Zs>Ju?^R#)I=O$}d%axx@sjKYMJ|^Q&0EYJ1xUjL|t0O5Vw8g$|qUPVq z@KurC2f;{v0L?2o8~EI;fo#(gXCk^0N#rR9@XyiBSO?Kbuwipfh`D9#gg)3#Y%!l- z7?i614TbF0&ZeajuTOX+uT`Hn49K}}99B8kNIo5e)#S@E(^%^^XzYXR zHewmqkpp8q+y_WsIc-FB0`6Ml7Tnky{yFGD0ZUAueXM4zDkP}PUYS=g*d?ExdmH>h zV7RH}i3d_C9oQML4D;!1$%tO%I6?Cn0&XX9EmzZ6XtwD|PR;vQ8mE zpso>d2TR#)`?JJiV)3%AeZaGue=C%K`zTdYC1urtEGiTvdb)2K9{Z8&8nuj7h|oZT zB1)w%ln#-~!4e&fmL7EEVpcRcpQ&xH?j_~Z{8XqBh#yqeolBK)?he)xJkokZ*ubXv zCxlnTQq9Y*#i^r?(DOpN$~UkXjKjlS`ZLZuFOBcW9<(Uzf9<|cOt?2+Hv0DV(sg&6 z-J+DKlKnuXIrpcg9h31938m|0{qobHEF)GQLL9k!g8Kn$kgyd`9GR`o6xv9&Lw4Af zRjgWC*V- z%jYDeZ@8<9T-n0DfkGB+aORn%cq3PPB2DJTmf`bM>N7Jrzdggj>g@@CR5YxWn24W- z?Jy8tbRfG2+7&lD1pvR5oB6g_{b^jXX}x1qIzmUl=cR#B#dM3NPGfs4)FF_@gvDj> zCZ;USK63w0F)AN=8*mVK{B2TD_4Hx(b7m#Za#$EyC!(Qg^@mc#)LPED;psmym!A05 zBAg!QXBK)<(^694BwD#M5M?S35!1EZDmcx0X7};RgDVpPb}!ZL7v z0cp9K8$xf=wv*Ms>Zl+}Q&lzcCf zxeay-uCLv{F`}b3DW57#k%B#(fpgX>ZflF0W>?FB*8^S$BqY00(W1Gs^4d>FI1c7C=wbf5px1>mtr-m0s zh=u^(E0ES4#6FuiCECAU^Q?+?d~X~(KEQBysVJIvYnF(5R9Vh(?6!aoINCc`8*;h~ zTg*JRS2Ml!H`A$uD{ATSHH`7;2ofxEJA#`YGgIJvNmKqc)Nt6eH$o#O0^QLmo!v!q zv|gdR&A>#3>pjziq+!&cI0SG1XWDd%CCk;$ykH=!+g|H^2oK|>1hHv3OJ?;(ntOoTr+PKFv-hN`v0xd)^)K-Lr8YQ8A;r^Yb#FcFng1(yV&a@x}SxaXH~a&<|0R z7E{2XqO+nM7tlLKAUUZN8fH$sw)a&nEH{~?HS%k&zMR3-f!nDb04^K@&vQ1;xl2Qo z26*G-^yV>hVX+=1Q)Z_zyRKGz!ywy*u#${0K?ee2!MQ^Livf1e?-dW;TaO*^cyB1e z-x!rl(bZ_hmsvwuww8-Ig#diy#-mAEhA801*(?&d1hLey>dWepkP!;J5#S+C8Kgnc zcrxKIDnzb2!#yy3kl1CJotqQ?7_P*)I+8WjN5I8rFD^h#AIPgtffP?rArj4f?3j~NdGJrb`ahPdPR&8 zoaBicj8MuP9uG_?Z@uKZV9HK~&u>Tdc8d zHy4D+em%~4;Pp^MKjSU{W(MQ~3-8HPP=gB_0Or+KoF69!^74%@wZ5QADe~uXj5cSp z7A$O$nAqK`+S@o8pk%7;dz0vzhy1+O4Q)9!g?7p+^HDF8-h7@<5Y zk&w5glxt;G)}i6Skb*~vMR}}FOC9oj?`%>sb!>7>JQ8ni8LziuoG7)GR7E)A`O{ii zX`pm4W5q{BdPHclVJ(_+M_Q#rPjZi%oL{{h1hXAUWT`4 zSd8{=Uuy1B_{d5jUVrLnuJ2aqpknemK5-%~?d+EEf=@8!^Wa#1K4ZO2 zyQ=dmOi_CT3akyNAWPUz06xashpAPkhgbBu{8cy1X+C*2U-@)ebNsD5Xum668uL>l zy-r6S31t~IM(+SbM=>A`k>PwZI%zC1 z#~0@-)XTGWe$=g9CgDdLul9-0jXxoQ&_o4tn4ta^8RL{EUSbu#mnh|-G7*zr7^n{< zBR?NeBb=LYv^_%>TNjxSthZ6JS!bz~_2?`j%LED@9(uS{OtI~Nn4K6=&Q24rdU>|J zmqI2Usc8bfLOjU1-CnhhH5a+yMY8CvrCcKqUrtEv;D;{5S#``-s zdaPx{vVg#{>>T*(DAlAa0!y1+sRR3MIAvY)hq>XM5nY4fk6Z^V7okI_^P3YR+Kzz$ zcwmcs`tUzab2*XiHB2gc00)v|sN24E5Jc9<@eS-}0YZbq=XEyD8@as}0kvM9xe?bp zL2X)Y*;Dpj4=upjAofFB)F$|ajPS4DSU|N`mS=lN)23JE1O_#Qt z8+5ya32(L-Xnn>2vZYK&Q?{$ZiD(=CxWF?#7D6$Sl$@N+6j;jBfO9 z5DA$L{r%kvrxCf`9;$@fvq{woHi9ELKz6&kjd<;tM@biKzMgyM8+#B2$o4&$W(vMAWU2@2iI_k2M5WWLc8$7J-rMy2@Kq0T zz0>;W6>3s?N`4wFWpkd^l?Jm6m9RFzt5ln)elFw4q2g6ve>KJ!Cv%Zt-E1sPtV!_a zAc1bs?(W4XZ?F>)JC~9X^3b!9+5XUjW@a|pk9~o8;d??7eKM4CcYwC!H+YOc0KZ$e_k8Wi$s0 zlHc3Z(+FsGZ_@3;jZqDqI=th#1_7vHAjo(pg70hnVJzG8(*^q0AX{Rscu4ws?Ht{} zg;&DFMLt}c>PpHvhg?}B7Voe&mE+8XN6%|sFPx3;de^knX~*zOb}Y-dT0SRf9+{OlMLm(4i{z`s)lCu*xRfc zTeS%Z!y?;!D}5^A#%KKQF{(?Lc0KhURv(p#(PBz1+C7k~f8jk^@f2XA=6`cxBd&~& z8b!!Kbgp;lE=Cvlu%J=}ipu6iU=OCS_os;?mr^$nWm2}I!&bvOo|qY@GpRPln(ArP z991GY?m(#AdhA$dPkbk`V8HmoP&4iPif-7#I(9QU962jR-xz_&ph77hGF7ZA9AUIV zo=5b;g_sqoXnTQU)I?mYkPaEUyl-BkPe$+`S4F^X0bx_NWhWG;-yffI(H*0QNY*O) zPa#IML110?#HBDyV62|f5kxKoVX*abV=+Z4QpV~6Yk3*+OlB9z5o-Hn)}0`Tle%f7 zcZX5ESyC`>gtCJsS5Re*vv@IiBRR#Sk}$<+Tc})J-O0RX6hzt)3rK5Xlh+DqV=Rjk ztZfudN1R#a_=g7iO)YlL5Ss0dJ0bzNDN(0=l`Ga!~lUv@=jUC!4_vJp=YHBEL z-_C{DGU~z&vWeV%qcL~2;?N zAReG1hy^hmkT%qWxxEG5NH$9M zxH61ZwVnuUgImH2HX{aO9lLEiCAWoip2;NtdZC;0gR~$0`isJLv=bfG^vxA!~#9JVR6H{@ld|xAMPM`P0j#uV0&NT zdh)+^{)HqDij;=_oYU{A(Otci(D zC{q1`P!(HLtL$y8s0=3p`013qwXCF+sKXr2_jxfRZ$!S1LL*57pqJHi%k6 zwL--l)ufyf%w$_VY{}hJP}Sr#YxvWs;*yWjPvBXf z3xYbx1uPI%2_)H>3;>{6?9=|gf4YD39G*XVm+#K~H%s8~?a72w)h+MUgc3ugBAj(y zpA+a4lMW_i_;sk*aB24v7c^w{u{V)WDOU~m)Wjtvm>Vnind#})mU}W$#rTwPi0nME z1lyXJn4SGdFT7{YFtbM~al@@UWRyxlZA5~qVkDq#T5L1u6xV*pxxH_Vk55co>hyT7 zFySjovzCl(E^9nCiF0x)A&uqIRB&ZfYjldE^>@jt02cU)g$5yetvjBqxs)}5SmqE( ze!(8efmF*jCHlPK`YJI6#)4V0++p!%;gE6c*DCMTfNBjk8dJ6JF+9hdN{I|I0_L!b zl{}*@*R$dEzWv#>NvKZC9h!&!_%v_a?nO`a@lMWMV^39zD4c8FOm~iFWUyCAv-va0SD@>!ue}nSDGqYMAdLsgjfw&CG$E=kX^y@D(SjYvT=9~qmZ+;C! z(~R9lV&e7N3SF|URX94Ja)_^z?rJS6jExR&jh&b&39kLYPaaPA5iQ6q+X)vYTwGn8 ziqtCUD)*xEBqE}>=cAER?Ko9fHRiphc95g9bU;-QN&ERUbTIFnd$3}}Xx2FA(Wi&% zOK$#SiTtVIZ!msT=$NxXI9rUo;Ml!c+~ZNqtKsKsW1)hPmU7wJ5j(0W!c(&$u4k6K~QU=<@bI8l|oEI0QU;e!;`L#~`># zt_2x3CsVLK;mUjvkQ{ct5}utK;Q$qHaZAQ4=9G|dnxQ}EP?YKXt^-K7mH{;Dh{EFq z(|n&rrq}JXmm4^ow$~N!p8@00C|)k1Kt$U9+us%ZZ#c0T9#XloWw@`I(djpKeR3JO z15+o(r!l3xmejVCK%-$spB_T{|2H)J?@^`d5ru$OdUNWtG^^{oju6R!DMUSt*>>-v znb@@2?D}uazlra&9uMYtea~@8dNm~^@UO*7j_7WIbrZ42y<)8HhHZ~23Gg8g(|V)d z{t(uPE*P7Y$J~;qQ0BWI=ha8H?v~_EDQ(EV{UP($ONjT;U2M`HR`J(MC4a4_O!{>D z+s-)r^5d_U@UN^(7k8@se6{jd&F10Tm-emS{;>M%rQCO200i_-3O^5%cT#vKg-iSI z^uo_-@|_gkN#XzDnbHxPq&v3W-lqY3!9eHepm&%(s*9|&y9MO z$Y+7YhaHOl`9kkKW_bJD#oOKO_c(TGkqS!D3%$<1R!wAY@}%7SDjBl*5V@GNNF+Oq zCeuHNUAwdcY1Yjhk=s;$Y&*gP>k48S`o?|)mYuk5ly4;oD|Esg`?_oKlTSt<#tOEa zP{ix0sgZ)F6upuh5+jTbP>N(>_BuKMFVcQ5Rd(OUp^ZzmWzb#eG*W9T3q__zB6yBm zXoUS;I2`Uf=4YQx@Ae@{qAfz0W`=Cf)QY5d9@^NSr01P*PVbFKne$+)M&w1i`!%L= zQ6ZL0r)JZU$#RI59W}!OnH!fk+f-0qMeB<0h>L(Y3h7}_KmF&@_(xa&v+xrCsJ?&w z{J((6^QqknWyz$N%LVfhF@y13BBIe4RF#$q?4!HnG~}e)?qb)&Gqr&P1Qt zJ-d(~23@O`9QfvM4!T$prlP|ozFx`LM1hD!!59CmKSxL<^XT{#!u5ci>g%Pp8tS^Q zuU+#bsupl-6#-fyd`!2fnD(br22D-*q-UD)^LZ8{!s$FyqcL@-vqVJ!Y=I>%=Ui!`OEUc`G{ zkrkxZeVG#;LqD~v{Uw=Uq<0RfBlKT#&IrTN$}_Lo2kQVt!rU&+SHh3=?WMQw6x~?z z>tBEeIGcyF0Q`}aAnV|Ep4ro%V#vuR{^7Rf6a zZnEKJ7Vhof3|KCCwkVFZ;bL}C#gq9FMb`uoZI6Ix+E=R1|^vkmfj1p)odu8UOzto)b?A5~S%%`Ra z7U6G1LMFG}5?p`jlJ4ziycb&Eq7Qy){gUqyT%IhRn^ki>qUlYkf=!|1*%E3i&9?&q#W-UZ)|KqO!VQ}-}U@myu|NB z@;~&%$6vOj!TC+XM`i){B>4s_iz7Alt1X>hPw+alvS`~r^w{UyDG85^Z=N3b>Hoqb z9@1R5*>vbTP$ta;ovVEE+k>L~cr~;V{hR;2l@dv^xcSfe(^0OYUG!SOw^q4jqT6RQ zDEUDb{#92?dEd-6i1}tf0A`;Ekc+oh5+^g!*Z~4TXv4aadVJ|m1_`EuSgky7vk}sE zdd+0#(=P7w=E)W@op>@OHv}L}y>ND;AElPI3k_$Zr$WstRTliQaXW>i*f0Lsga6ZO zVLP->Hl66WNJnnCTiAsVwvhmes+rkVjJ{ZW{K$*M{JGHh2X%%*!;rS&9qT7c$K$%( zS7~MssuUtJaYw8_H(*^JmEFywO(EixdLr+c&79kI$6X%$urkWX@Ux~JyXBeTDV92N zPX3ol>|W|Vc-H;mxYjSXUs}%}XVcr`oo0Gaw@R_cF_->9o5!v#hlgEE`So^Fl_;bj zY%GJmp8sQ@%I(4*WW9drV)~qM0}lCUuZ;bKf7sPTPWCSo`@h_t_Dio-{G?yJntgpw z>et)Rzx3Mpi$42z5Bcs*z8kLZzR7or=bd(aH--E+&6AN|jvsJ_3@OEaXh-j{9OWcp zZU)=P_gNeKYWLyEUJDC8j4;sB}}}R~R9|`P_y$<8GLJAU88m1zb?;&qz;; zp|d^5+`N^E%DleF`Q%;0jM%Ah%qU5z)g+N> zxY|InL-|AW=)SJ4EfSBe(ElhS?qehQl^Cs5X}$e1hOyINt^CVQf*G9~8ra0lOb@B% zGV~S_0dC!WH)46FGf7N#8vYsa=I@{Jopb83c1wBK^%!{iAq z-zi($Zupq_C#%^22_-%&hfvofe=gKHGPE#$KK9GeY)*o?1AXBfM|-#;sAbn!vLL=X z*{4#e!DKqRqoWnMn{s0zh6ZB~4?Tdeisy6kcUHqz%ej4I{vaWbtG#2~J&I{!&K~Lh zFp5;>GI`|BPZRe4(!FXpw-xEP$gM2KePSQqNl$}qa;=@25=xr)_9b;OKgf)g6+5Kg zqv*IlbS|Cg7PB43?s*TAm?LVKy*DB!vlr~+v-1BkwDVyKHQZK?M$>7RMrGpr3uVM4 zo8;V4!4TOJywa;9EKyO}0z(V)FaAf4+23^f5IDA5kZ!zon-LhaD3A-FRmJXeoS?%@ zdxgF+N}3P7(Vgn7(_jPh-SJ#!WdQ6Ej1+TLor0PoLfH?03Uv8R!*74SQ-p6N@jv|kzojBX)_pm) z3%N674jFUjL1~0JX=pYxo)6n0Fu~&1D zyiyFLjl{KJp9M52fXLj@!GrxYy<@YUG-CR>K~fAV5P}rD+>l&R1y)m^2{|HzGyjf6 zT}>vHv$RDO+`-~4w+nIB{E;55#HbT%KPLoBHMOcAT*$_E=@w7%Sm+vHomHTc2as*7TR;BXX9$BMSe8T=0){URdAy%P8A`eT)bq$1Nq zBQ-Tso(z(6VT0>XsIw-t%X_5uEPw%w6xHV9m7^`#nhqC^UwmS3ee7P3jlU0{IqBpi zZ0=bH4a5Z};D%!Y8u722UY4Y4V*zDN7o$+~eo|!}_23znuX!hw}6OZ#?nI zB@5PdxsZu~derkFHT&Nlxbh|l1;!Z~3oDRrxu5>@@X|l(&w9j2r1g-I9%6jh=#d!? z@875XqP?w^e3l01g1P`^?InwgYcO7Ta77_pAlFbb=|2e1hnLS z`m_>98y`>UIqF2Gpn@0kjqhzq(FZ9evka04K1OQ{vj=$IW@)B^HsZ!`!OeB__3-*W zBTK~Pq1`Zn)rZe}v^ri*aJ!P%1T6+@+^v<>?0E-scd<>*S*dG_W!;T19zG~FyKs!F zTjsgvGvPge8JwTe2AC?Z>p*OwP^fK_4hIaiZM10bc^NL}8y@K59*LOCmcC0U@dDPK zQu2WASfBy~!+52J3;lL?_fP5|FfvXNafnef)NWrX#I7XQUah2&sx1-S)3|juDKUqb zR!+>yUc@KfoHbGp_ZpIuuD0;6S@ZpRL^@yGqi@5naKEgTpQ1-^F$(I|HYtb(L%K>5 zv!}%VHHN?AheJajgz(FJRV=zVYa~`GNCHe$B*ImgS`}iBb0;K*3wnb|S&5sB6{(#c zQG{V8q!n8%S0dDMT-EaYxc>6hm0|r-0{P_4l>M)bAp*~m*Y=Lbdv`O5(PGb>U#MWl znb%EkJbd44{?;HaY{Om={k9UItV!gt1=WS`e*}=|-BkG_J=5eOdo{z(HE8n|It?!;*o&3 zcCJa%E7I!+EjLChu{BrT(|TLP^ljHa@cJBUe0SsF#`CX56W$hi+w~~5?a$vm?YnpT zZY00^Zr^FCw|e}of_01B`l>??MlDYdt^Cfp@xh@r7rn0Ku9qm~*=H3Q3Bhxjl+d-^Jlmn>7mVSU z)JR3M!LtB6l4&}qJr5izmVlM*&@i<=x%uJaBwuwI#UvW4b0HKUP_e(pfoczc%v3)F zt&0!EFudjrDPh$`_p$i&OeaK9dO-HPOGC0+3XuJ^ZW(IYWhk7kx!Y}KHaNhz2={?K zAJ~OhzpPl|jc5*X&IfyU!(jEb#9)r7_K+!MGVbOdthSHnmd0h0mP2>sDSY4A^xOxA zW@zGYhecR&0{7KU&Kr>^kG-*t&^)&HHg~u|6y2Fh)#W)h@=H?ZHf`r0(UK?tX06kn z_2>29?j%6GmX6|b4+y=y-RCpYZ$wBoFKl-=LuLx6g_BN(BR_24p4$f})M>_t?BL$s z`$M*Hag1&Kzu0^8c(&8Ef80!G+L`HM45h85b2z0as`h9mRjIb|f35LJRCLR(8}iHt_1L?{{|A+hiDm-C!$&i8k|=ld-4dVb&E^P2pT z-1jG+^}3hObzj%}y59XuWGz`w+OyStLi@Oei_M;Qb=^i5hy7Lw_}6nJ^YW9f|Fg?f zzagaW*sKh$IKo*DPlC^6k!^de zselA8Y^U6-G|uAa5lAnBU#rUeh0^ZelH0o9fAVt27h-!~yzXYa-D779-dpMP8Pq`T1a@Af2mcw97fWq@YqA zAW2DR!-`@osYH=u@PS)=)7)M9wtEz47VCL^mZ5rZBk+(1w!&1zwvZw(&>PG_s0oZN z4C^qcfLBSBA9D2wP})lk|2lY#M~OY+DOZkSb(hBn4N-tjB^uLRlNA%G<-6PRTFNQ) ztQuK*_~F=?1q%b8p*78+=_v)NFIEPn{fUs5;IO>kv_8(afzfU@H4PWutA=9>C2>pU z=Cl8{%Q{fyv?8r@{zz2OrHotH)9$sW^7!$+J(T<{KD5IvIU73jbZx*wd6va1(HrQ7+r~=KZV}PycII_>UfaI{f># zo)UaEa2rVaXwvKnEPP6B|GtD0nzrY~s-L**KfC$j0OB=05G-JmylydCyA@%%=`QJHyHVOV@eknXR;p^9L zBAVoamyP|=Sfnv!O^^^kS{tnvy8_j~K|oJWo&uh?mfA`>y!aoL-EaHeglltuvq-QwOQB2fVv-Fop#X zw7%Rd5XRQtmXe=o&&m}HGW{s+Pw7`~OjMQ&R)bT*H`2Y0Fhsy$Jt?P`O>*?v^@ww< zKzLtAuk;#xn!NV%P`38(n)BE^W&^#+1mGJv8ipa5KR(yL7pBI^sGgUCov7YSm5)y+ z{WNjKME=q{(d!|Z4=rmxZwi{doP&77u1Q4;U%uI(K#S#k^AS#=3Iy>d-NOD zP}a}O3F+ZL+E{&kv?3{soDBjwDsnT2mhvEqkR}yHJd>(f_4D#iVh1c`yD^0w3+k=A zJNtAIY5-^3L|eu+lSRE4J$z=n(_Iw{bJfR?6ZYq#^YhSw1N1OUI2!jQ)e}A{>`S9S z%X?&gmHV=OZ^e6|-<3Czf;@(*aJdvyJIHb;LWYgHhnvR}$|(pUz{0xTkmQtb2ioY` zrSl;r#Hg-Ajb)_!;-88AKeFwkrB1s^?eVG{C#r%uEk@%q+~P8rOKXMJmCVYpHCWmkou?aQNaIlA>~4v4Rm;f z9xv5wN6rQ4ya!en8-LZ`oEKi}IAbUsD^@TlucH>7$!A)*5OlIQKDX`Nn_)aGH`OsqQ3eNv(idt|( zuo;z-Q;hF1ODWzD({E?YPQexgCwSM#1R9OOE-)X*8FuMtY+wc3=QT!BK_2Y21NeB` ziHGt7{#%Cb|015>s{alN{LhO0T!hU`Mz$nTkGh))GhnLK>z=~!FYAnoEei{jVx)QzN}a}DMrP|wbFoTM}-Ut<#WLn1FwuF(mFgin$fqhpL z`t(A{pFW2K6X>0!C7XCytjP-HjbytmAxQQnm|WWFK0N4|^r%AbiD9-PW%s4l*`+~T zwotEGJHT(S^A5n^7(Mi>;U^JzhihDC<^3&WXp;$7;It*uu0OMSW!8p~yi%0YnpD+V zd*~Vv3`N={Tu89Z+3rs+p07yp1dQbS>|Q0MEc4iR>l2HPt`F#NMoy`l-u>xoAg>{> z!2PUvQb8WT^W{YIEG#T0en5_W^xE{<>{f$G%+Z2xrBcb*kd@|8=Ns{O_FJV$z{UUNYBXu-f|d3qeUU{GGDpH6 zi%_W4t{_zvUp6EnKkZ@UwsqWFrO)P0cX&8#T;|FmY1U7SpAI$d6zRU168QkE0OrAG zrGu7yH(y?_T^FQYRaI#=8T@QO^9C@M*OVMdt#3>-rfvkTxPgM0cTo!zAq7UCanusu zcfXFzXGQ$$hWhQ_|IZ}wKM0X`V2g&#^#vs^#1FcuSb;#mUR}L*3!|8qf$Wp7cCA)s z1}Bf&GU|{5=?JZCSQ^;^)@zVf1eSMoF+c zK^|%3-hSHpgP0Yu@22>PBIKm=!p)y1JooihbNU&TUA^o31Jp!#_h3DR<1lzS5DJLv zqw^*udtnyi`5E*OQ{5%Aw7Agt>+)ltBF@zY`}UR#ibx_iQkq&|g&h!)y$JMlb=v>r zMpSlbtWYh+R6Ayeo*#U+PIq;?Kg|Q>$f%N*NeuV5@l}Ane@0f|Rie2TJW-xP5Vhx%5$2Mdk=A!+u37S?cr#h5li> zyPMU)qUPj;2y@lX5`<*GIZT8?b-xevbieMZ_+0ugYs`uv@M})kY6mc7HZ9*ar@CE_ zM5E#Y^71O-iI0(wE0uLxdMboIUT*2b5xBdK+q*Q-=|G+By=$Ur$%zLROQzh!ZU8+t zfKnnp?&FKfDY$j!7H`|kv&-=dtweBk-=k*aIu?A2)tkD(T8RH z&U?=5k@4THDYU$mH3uD5`pNLh=}(XP{`?P<#DQg)u3i6fHj;iw-4;S`K6rUZ;OHg@ zXrkr}<3=;ot3%e^w1X*P1eQ=2I5|JRxO@=0BMgDJZday$0iHF%5BK?7<^LiHsNMVp=6duOMv=PWVDs2z7IG_V zMg6VPxg;fvhHw5G?)zQHY|(sW?JfX3@G3Cui(ghe?00=v-I0IJ#?tc6CprIkJ6f`x zDSg)DgPTbx`QG5S59c1B_4gs;bpaW}m<&3-z0%Wj=jrTS{HblX8^mt{Oah1>+h~*h z69%oOBCoD!SRlpBoid7oa%JBtsJh@twZ`XWmMcNSjw?+q2MC~_CV8re+RQJh$HRj1 zfvVQ$IQB7U9(b6%ZP|M#YUegY7|`a`PD{AM27lF0L0k`;fsz&kRPS2~ym!g_-_4!j z;xK9GI)m6bJ|!jwkE(Tlcr}Sg4-<$-#JKuiL{jCjyOGnJoE+6UseE9wgk{p9nRMF$ zzW)$e4!m0kmL<2`-=hi|q>+l)9sGM9-uBkr>{i9~ zb*K#u9XoNO^6N!Z)YVZfC&8<2==;Svl!*K!q^T3Dg4WV6 zV&MxSfg2X)SQY%^33HpYqt(?0_r7kvq>bobjH+3^yt{ipN*9HkkrD4|Ad&(bgt0tT za~mVE?@IZG-2%R_TKIu&(OlM3rp;k9JvuxPMtg}&<)KIs6?P+}l=byN^V3H!JkkvR zKJHQcr(?Kf^Xj?_d0kOoHUx16UQPvL#v+&f3vc$_+h@|^%p@MU{3gFdZ{VTC>7w$R%=GvqcovGZkiEae%U&!Q%J0amPoo<0a2&cf_8(d zDPhZSf&su^m)%<}aI+wq=GW+kJi=5L|A}Y&^wpTdd7Xr0c?MPC^Awk5Xk>q5HH#7u2}8ha=b+jg&Zid$9*FD}{S zrN_(%t_mwMKB_Lc{eoohoxgDdax4mBp?!Y+X?ketD50|8r^u4jrr^MqYgcJ^ld82O z5+6^VV`YcqL$3sm<;{zn;l))`of|6_x)v8=LQ^)tF9`iU!xTfQ8x9gla!6SptBT$b z%TLFp0gG0w%+cW)JB==P>e~|UXE>-J`?&yB0y(k!LXGJ)H$gEbVRVkL+ZnfPy{zDN z!w@@E%=jy@du1+Y<+)EEee)-&DrdFW%yf@JaO9NP0Gn`^{aLtm+>T3~ud2D`@*3bc z*dZX)KYg%&Htowvf|z+i5Sxt)_hb4l=O){?Ic*ctf*w)AxX!Mf@@J6afn~;d2BS*b z%YSLF|I^QZhA|JUY^ezmZXa^nW8G~u4Q{ZrS-P=iyl#YzqTWQl0iy1+qTPU&f#hu) zqk_YqCU)1iB<@df6KaXLqMVvZQ6S@6v^vbpC&MV82rPp)jPg9?R=TEbI-^#eSJ!dQ<5ZK=tM**T zZtERfa&t&ZS7NPD;l%-|3RL^bEL3=JKFs{Q&WHBXWYDn5V61Zu8dc~<69a7M_5AD& z`q=n?O90pZsSSSn|DQ>~@Q{uZu32OJ>J~{Wu<;k!u>ayX5&gxFq7)wf&kq0lw$m1< zsLeodEZm{Mr~K-6qE(89LkbELiZ|ZvusabbPZ;4!&|5%E4- zi&3UUEV5hNxR-k&5hm!787+eDe8ym~9%DxKZ8GIyrq`#k$I{2t6<^G)lBA(9rb z?u>I7)@+ny@FbF?8@d}{rZ^L59gXP7zQ@FIN$!gTE`tYxyut( zvUo8ou7M4}JYrTXYQlMn5G?%VHAdXX_yRT0bJvj|3O@RgNqb94`({-ws3kdzkzkVK zOi2$cNg|M9I^Qa(3srh^lzlK`-*-FE^6|N#jly z0_`Y22jjYD=2mQ-J&TV%?94D+I*^O|Zbm$%KgS+!kd{b58SP{9TnkP$d<piil~6CG#;VI=F1z`&?9rtjrp5c$+tXAluF*IY^WM9QyU2ab5(EVJI?tWGA`e(`ib z3*AjG_UT7wd`O{ zRcK~IS4V^Ttk`LrSQG>DHdQ2(;M@xSbyO?`0_CsHrW*U!Smm&zs6{_Pv9ilY>%Hk3 zDB<{`(qr0*zxSknRXVWQo;qxWE76bv5j+x%E+M73S$3jI@GrXe9pVld=cau<=FlVr zjZ@3Ytk0j#k!JE{%c~ycL+wKhCaZsfT-Yp`i+ak0$@IOEL)2G!dWQ>xNK#0StkC@# z!We(|5B8tlnBW@cj~+bcJ$OT z0X+nTug`jzc-+&qKOy!NO`zi(G$&fL78aE`wZv-b{))HR__@l!O7@=+;R7n@p!l`sujep#oIh;jT z+|Oi8R~m^BezWwT>5NV?5nY-4IBlV;r4c}^fFYb%)mOQzLT4U$85s-WRR~ok@r&8= z{`E_<4N|u1v^A`Xc4O13QUo}aQGysxB&%`4=CO>JLFYBKZ5htlX}YA zg_ROK$DWp-FlJ`37q(hcK<22HExA2bKTWGz>gKe+5uR?oYBc+duyh;PGl7<$VlaX) zx2wm5ByvyPQbgoi@?3hjs@e$1R&oA@^}V@NzmkftufA-IanU?<_*8at_~}zT>jeS9 zVGv;1>FWM8pL&~dr{KfH+TY#io@qH+z@KU{HV4IZSS*#FEPoLS8CQvGKn(g-X5P}A z@FWN=hTEaJMKD8R$3S(3-K7yhQ2KOY?+ZfhA&h2`MhoX`J=$I*HCF)Y8$haCkWHj z$~qXY{@S##D_xGGZzSR`!@g|_!L%+U+6j94>Y6a{f}@t50HErKmpR0R@B-HPuL8Qsq53c}Ua`vPVyrlA zS2Ht6k+j9mKB?^3?Y-Y8c9)@idA8t3;EzEe>r&0Aty7*009@%eRP{GE< z3z`wd1gv4ckEeU8t{zi)Zv8@EYAnyb71R&}otO_)(8W*i>#oW|#UE_DE`@%(&1|n; zKpV}J!Ng!iCt9)s2efVDP!V<{SkHlZS2_%uNx_mh? z8fWp8{Pb;jPJxy++qHFYUu~|qY1P?e&eqbur>ET>Rt=(u=+3Xf$Tmj%_Y>emd~Q*W zSoPanwPX*JLw|{LkF>vD<@;_Qufo{N*ttP-3rFJ*2Z04cOY+<0Ig-TrS7Y{WFFJWK z0r|e5VAJ^V3P&}*f*oE}^}|D$;*`{aBOB0z_*}Gsf!^hQDvXWQXA~Ke*gk4p&&2-z zb=OPIy8~t1%+IHxh%sq@sFV&EjZ#1(#v05as>^^(V-L+ukA{o^>WxLCE!nZsI&*A{ z`^Y#+Z<0?w5xZ!SSKMS87Ip)T9%+E((2yc_dlA4As4@UA!KGE`I>3S;IGZQ2J_QAE zMl*t^=HsTOa5npL&SqyTJiv?Af{cu%4UI0lObeh!Q<9uk%Ql(>2Ipr(|;#4%Jq`FA^W81u{#lC#3HKPu$ej~nyYgz8l6U6_-U6tmTS5xKhQMGcuI^dKi}}_vAKy4LoJt25^E@OP{LJqz z{}bwN`dodd^Vz7R#PCD0@Ke@V$oc6J+D(AZBMERkM+%fKaGgIoEAnM!gyYZVH;&&f zGe|OZj8bp8_Q<;&h5-0)y=vkA$%{_P-H4^u`^XVynX6=vE|`UB2bvEOpz zo>!Nj>0l5_G~i%VaNL(;n6v0iHn7bXu@hkwRh7tVI{aaO9xTXz1woq$*QQh@KEn^o$IY+~seEOm^O zou}uMUkD9{HgpZ>!IsRj0xK`STuz`7WJIEbn<%XA$rgd6NhUr)EU2!2BsPOl>$sP6 z_Sp0v-LK@0ywr$|8a?#!9E;h~)Hyv?x}zRRJ^5;bzH}UfD8?94u(kc(e!BWb#6t64 zKW6Z@Zv2-o>EwUPo5Lib48@0qVo0M zr14wf|27G{^Z74Q)eDJ#w}J%x)l*{jXV1xv0}i(T`sO+NcXtF50VcIfph4dZ2--fK z*Gq}-4=j-?lm!%c!}PZLMjj-WH9&NGz(yrg2k7-v76 z>ov%$Hvs~0SAkWaUYC2>SQk1R>2%ztZm3S}Tx5wxYH{iw?Ky1#tt;jS;EPH#{i|=i zRa)4TA97S>^4H+k+IKP>o^jrjC+vZ1-miu$1axQx5+6v2qCqUV3iAUg#nt&>tcq7vI=ce$)qJ+l1%=#ZG9aOw1sRS zU8SUKGK|utHuKj7xvw@JF%XnBU(j*a{i}*W*)}f$Lafq9czfTht|}=xrku3hd_Zb1 zyo+Ef$o?3GU6!nl3!9tE$6#-jR9m8Q6Qi#$9gZHQX>XgtU#;?%0kL%i>9%@6yBCHckD5#<^jr-?`yHraz;*UH~A_+_M=fCFmz)1`c09YekR08OK^Kaezo!9*X&$;shUKwF` zqI@?LffZ`N(+|+6-=!EVzQifkpxJ1c>56Df?8lO#mXOkwWD-4chg;bN;(CvlX`f$< z-vfK_YPP1Y4ArAd(%YOQ2;ef;LOD{RZ;+u=kudSiA^y+*cBTK_*L#dXvk~{eO>AA+ zG%-!s0itrIb<;OTcob*8w*|9BFGTc$-{quSuNSV<&_9?gP=!F+Q1o!BB(uy1cPOq^ho>t z>}}S##fD>9bv-gJo`HX>bexwmIfa~GAdseOkq()%x#~n)_nCmjjn8nW@EXm2(Ahp> zAt-WFJKFPXdAfuw79!l+Rb$)74@N5%hjbf4ZTKHXHPJr937RV^u^NX?G57|h%VI_P zxe%wV`K*_(GfU9SB&|?sX$-$wV0%0vYSrLfl9`zAeSMiCj$t_6bCR`gPMtD)Vw+HJ z8&d_Wh&HLJim>(VL{V?%0Vu7TQpI{5C72+=ARp_EtKK;LeW?n!^3ZJui^Bj0NX2bl zQZ1tu?eDJzN%1#JfjS?2|NZ~2PyNTY|9alcG$ya5ap7ST6~HA!3Bx+~a*{46s0Ghr z&Jy7GMg91RqR~j~j3M!szi1|B>4J@c>SNpVZ}0ItMmL3Nm`HM|7i1Pg#J2;qKi_Yx zl3U!qwNx4H6qV!OqkNsCeN{l&a&5v*tZ8df|6}*dWXe*|aST+hmX-pfLDW zGg5{sPouAScDcOges(uUBXYHA?*00vnS#&+pZ15c`YT-@Bt^HH*Rl(?@JQbMX0 zTL>&H&K^$2>Z#a}H!{D*dF z(*xIVS5`)xWUZ+3bv72hPr_}t(0$%2*}YZTGA-LVR83y(o71bC^T+DfcLrQ|jG71M z-GebTyhTW1oFcg-oPcPx$q8xKsT{_kkhmr?2{T(v!d909 zJz1$CX`j3rFK~2k?t&`EFehyy>x|Y-Y_|qg*J#$vuow7Sq4;O#!-|p!W2?+){`*ya zOZQpIBjuZ$KOt65Bu~=J5VG^J+8r5`Mp2ZVkLSw%84Z1m%Ov5!K+s#IaPWBF#~TH5 zMv!>J!$1sAXB*6Owaqj?sL)@KY)<5nK<_davv#!h&3ZMNHpO{n$zA?d=oGEA!9gj?`Z5r5@^%ZR+a?fF}! zF=&>?FKf5Ql#8pTukRN!WN(%3T#yp?n^Asq%DCG$Mz~ZzTuzRgh130hu8omsD=Hyf zbvxijC;l~ol==lCgKD8Q z*HtXm-ju1?mlrTU49y?2Z)mT~8LR+|F*X}+&)SryG6zEJg&A`9Ogx%&Cxmr zGS%2~TYwxuO-a-w>KEo1kEW&tRJFljOl28kjpG8DLS}^5C9GW~E(rzL+51r#>agcbBRRV)Kkm;|| zS0oB0bnA<@5kdLKZVBf(2G2!BHp zF(ZM1TQ!QS>YfB&8J{cId`e+D*Prc~6_UqESXf-bk;9Gs8&j}7%fyT;!-m1B$#0bo zrgv=)E8YsErkhLqDo>uF z?r#{+7{gb9=T?B+Wv^(?((3+Ic~Qp%zBRpT$t$>jsB58YNA~mnh3>7uN^{`t$1a(%Rx44>2B3?o>~%O?_VFFmjsc$wYb zws-8?$DEFdjU*4m!g^q(;&d*!%=x*USD9ZVxtu)ya^;gdrawAJ*ox33zlUz)3UCG_ zH-WP;#P|CmzIR_$Q@m7@xo2`85k6B{75B>bt{e!N8I^G~cP8_5N9U2W;E( zFis<5;ixx!)*;Z0!?iEVVZG>m(afO zjmX}waoq;)(cd4q5HYLAzjtQ1wB>?#N~_RsJBXW{nleX_B)l| zf)o=ve?}8=wk&<{sU-Ew1x;Mt_gforF-e|fIwJeL7RgF^t=3cD;^st`@R8t7tX6(Woq?jp!w!}2v>o)*FR3t02V>s5ON z%!It!)ROF{y_8&(OE=Z2(OX2ghI4G7QgDN;R(84nC?7C8Jsy$qOlZ5qhs|kL>V!Uc znCmt=y?{W9ATB>XDo$UEoDF8CZ9J3+RE^{^Z2E-r6xXE(JQC#o3>>@mz~AO(`?R_t zZBK%GHnEheHnzW}6x|eD$akX~(U1zvR|t$mP?%ryK?QfTP4xi1?aj&85Z!J9ptVfG zua*ihtxnzfYr*N@aEw9QdMYF?T+i((wIltoQOp~O`c6|75<>&I9ZR= zkBdLA0s3k}BJ54iq}b7#LwGpf4GAgpxdN&GI{P!2e_lNQ^|>2MoaY~(d=Lt;+B~e~ zh+4c&qb0S;$8E$0@d&`BuSjsIn~j_YQsb;fY>#Gqke%ixo(y-}%X_j|1Cym4NzV+x zhL8(IK73XKcvR5r+?0||^|6=|je8bvw!BKLuFolecacI1EdwhI33}t%UpN0KWN4y! za~AT=$ALYukUXYjwPa)0KVqY?3?20I1;@3AQFfyVa*_E3V=B1Mv$L@eBp5wNgVn@D zxjr2-0c(*ejZft^a^8^6cqufXjqqql)E}6QjUEB96%U?7^)$msj53gq^;8L=q}+Ee zejV6*A}$4#d_{jAT;oo^`irun=e%S8S)2=Kf^OQ+Pw2IRxO~a9jv||~t)LaU7?K#! zn*y>J(38^pFdQ!ho>vpRL~nyPWmO@gsKR=SAn8v_s~5rg42VG0YJl#2+EuxiM8LpE zq-=s-X+P)srM2MHfF+;-d_k(I=h{me&S-W6T_3BUfC9Smd$RHG@tyGRUAd%^QEEk_ zfj{|cLG|?#zN(CEW2P6q_&j=y28Rh3pdxdKIr!E&u2Ug$2To%txWGD`~C)k63Mnc=+O zqsrqru$AYWcgXkcWH4fc29VkqH6wE{)ua-UB`06sf$c#JVduudyRasMD!qza6xrFE zzw4F{98=JSWEUqzM+q`dm6GO)D;;#k0(+XBKg3Mgah^F&k4J;=f>mmdOPx-oh1Emd zf-X0LYgQwd3O}E`{e^I+{z=VL)Chm$VBUbP=nlCIy*{@jU^ul-FThI*Znn{Pf>E;q z*2T`T)PoQ^TmPmhMgl9tF`lRMQ3YDxFktX4i$nE;{oZrkCp90ET%Zw9J|SrfYY{ZnB~$0tW0yQ; z0`n9MvAIyZ!lI>CE8U{46{dZ%k7sjg$gc|v!(gV2RGG7Q2zicg8AQdW(iM~=APxNx za3yj{9u-}#6~SosspF*3yN{AzC0moSDSIf_1VZ>`W9NnhFE8&>8_R6d1}+H{Ds2(< zk8_&1jHm#_$jjZON3fvm?#^c9JlndKWhYW~y6>aiFqO}&B+bk2G z65mqr43g7XX1`Ykyv*tzf)mjBl?Cv#?TGgG*gk7Zd#7luXA@r@rpu66oa-*b+wG*j%mjYjHDH4D);AQToO7OL$5{xNwgP>nxi6>B*pIuK2Xrp;gJ;Z0E!pM^f@U2sdzAzAXFw124h)e9Cq zytr!!{2rQ}P?Y;h7NXQq}~{ z-zs{Jw!;$ypMY#N-3s5l@>Z$MHUgIUecjIVZH=GXkv>HE(Wuxr!IHqC^P#%Mq!i$B ze{xf%HEm1#&`Em0^-xi;Ajp(5=2neMqNdJbwm5&9@JidO%$--0K}pIE4&F4-*pa?X z9v?vlfnaY2t$Y+P*^**;C+j*Z%ksvd$|iSJJ46fzxvnpy2K!-RIL5R>*LAL^0zj87 z2SDB`y_Y<7;3%duhFW>*;dSF6q)BJRr^^D3~pzI5+@n$ zfsHnG;pHzjD(hE9!QmRpT|1jLKB89!P-0ip1JPL!pIWfZwsXyMZUVsLa)0HQFRhN0 zo+GC>LRkzDeQwi;5aQQSS@ijy2TEd{sI%WgSWlQ0ef!AM%?e7=+besK-$O1+ty4}# zqr>mdIX#7FhKKmHCqhq`S#Ha^YIcoWMn*{0Yt3gBOt)zL@>Nfk>6IlS{A7UN!#vX0 zLR`v;qKI~Q%f3l`zo&QK(T}3q3nxbVcL}G|_-NyHYRQ(T)u(CcWp$&%K;Y+Ni75#Q z=N$Z<%0E=(nlC8A&6}$6j=O>mXIUxym!BQr{>zosKfo`;GqPT4d_Kz~&B&SI2w0RK z>Jjk^;%W6YYZ8GzW!ig>PQHeVf2)*L{g;d#e*tV3 z&4*LvR_zDzJ1+0U@;7!K7`|0H;-9L~I=pbPPO)uic4w~#6g-*t!F`X*2d_)^qclFL zbKO4hytd)Cv$B)u`t{}Ak*2AHOiSrc169K-MMLHHI83pVK8cp5Jt}${^>r6Jm{{a- zG`tqDZyFXl=p?zZI`5bf{Jw7hrp^BbedBYAlPoIX72me6b-8A2TWV2z9SE7M@%aEBAOC8$xskpq(Fz%SA|*1tiU7h( zwa#bBI+(fL)-7`nJv}|swG=zB49Y~N@R1@Q2;`j)Lx)WtBq0i{EWX+H1xLlilnE%p zg4());kanf9$W^9+sbxH-698h5A9TyWp$Lb|8bOD>Nd8+N6CRYwvr7a-HcQ4f+Zh8 zA_YnVkgX*Yy13Ait2Gne_3=GLq9ErV94}#wM$`1hN~{s-Fu<-|7mNIwnOTjG4PvGS+sYT&E?{{_B`e8M5Q z{-r@v5VDFn0Q5RSJ#`JeWT#P~Yw&CY5jqig>}0&{tn@i%UbHZv^?C+wZBVPo#e!nL zz_g8)&<3yua>FXiZ+_i1;;$B-35vBe%gOwzRKNSxrk%Fu!pJ>!nX7knNPq~`v#CJ& zx;*f!y1!@LQl#ZNO{gxoeNPxKZ1#*iF-9!2fT&s|KH5Y8eD(buY&yN09J!|a%)_DJ0t@)1uvz>i1?CW{;}laYPf}Pt_US#X zljj@V9%1KlN+w>zI4d=P{`B}s84qA7kM(37IwD7Z34WARAzrzrFe<`bnw>5&HC%AY zyHcM$dV;32S$K)|m$!t`nRRuWq^*Vn1hKoY`B@CV5*~Qiz0O;ZI(%*rP%ckg4d`ul zZnNKjljt^5lD_uhA&Zmm_Wfvu!p5yPRlkCSczHZbL*VHiQC6 zOWg}oA5>36D4aX-Yhi&HYnTGF%2*o~$o zCigS|e5gWSQqM?roS$caXBPU8m|cqP=h)>w(2rTh_o#6cWPe?=p)WHJd`EBd_0K6FDaFT+)hwgdca zu~0X`kn8#&ps#VwZrABbn_KTxfwpbf^P{@^#MWl7WcHbxz*$TDpe`;GDF*O_B>?B$k zVaD}G=xfRCn#C_MAxT@Xq~-_H{Vx=bg|_{j!*7-1Qq_Y0p?dPMbI5%}&z^CT@U7Ab zSSI#NlUbxzT=$rEuOW*cpQ~X=(vwmGu(kZXLHAy48arJS?(nq?ke9gUobr--Vt^Wp zkBwuxx^m1?=?0?PN!**?rKEu;qoV>2{(gd=SPPn&7F3icOt6agM#qxH_uYJSPF5CW zm6xhaADn!h_*SW7oPOMC6c=Z0aYIUqtTfiCqcnptXx)%BRh30cgg0htedU?4^C#IH zc}q#qX$jl<#_G->N{5%>z^@j2x_W0|Vemy{{tjbS+|`#BIeGbRtBeP? zG9<($I3!M!)t4X5*WFLnMH>~QK1$9Vmef7#ls6&5TFAD^T|pHgIb}BcLFGfm9A!(! zh-Tz2041(^IKQ~!!Mxjq@8mnD25j9f#v=nmGVWJaP{au{Vf$+~?cFEZZN8FJgG-8b zhQ<|56|yVzAZ>iNqDEc!mfb9AQmi!j|Jx`6pw9k)O&SI$IP3~Q@c^#3U5Z> z_0BCU{Y>?}?Taiiu}!Vu;i1JJN8c);3s;U)Q|>!`;PU+g{?1rkaV?qc*}(Sp?r~`F zW^uPd+k%yUfgDWC@*QMYUXhY?u?bp{CFQH`2~|y}6k#S<^-6*(YGwS4RI4>TRUiMd z(=*o&d#pZ5=S{+e)p_hAa)T5RNWTA8Da{;)=J_}oYG-+Z5PMqa3H9mGVw|t1B3-=l zT-S}v99$HJarg_NLy|A6SN5uD+K8v2iwF(Igw`QMPTMd8MRqSOBGb$xEpF%gQYYSx zcx_heVkD1xgnaeE4RdKOe@cPeFt_4*JBz(z?MQ7ty$0>$SCX$JGGdbM7pS${!1{eG zeB&b9e)uLe>YD@Dsqp%NxlNqK3_R3%U~eNL^JHk+f=Tf|-F)^=pZw=p%_F-CAWc$y zlWbzgK4bkfck3@+ha%L{kB|%P-*<2AMk)MB9$~dLKh72I>f3OpuPPcP@a#N??e6^? zd^9V=zP-n3wT}M_n}3AGLz~5zQ9SzI${Nvn<}@2P_|baQrgQ>J*-{Go_vJ@9Fuzzd zVqvlLQRbC|KLnjw_w3)jh<{lI>W{@&zgl;g?Q=SPQVCA@+gty=DAs^WG&EmeAmO0- z_fpNq?*wUSU5O;?UD)MXB}8Hfy1Kgha8mev_k5V{^{Oi?Znp2Y+j2YDhx{gI@F?G* zL;}=phaN_1Pw1&n#e|S*T{%Y5iWqKh{%#j2A%%1HhqcY4Gx>`aD+<8eXVQCY`N_;Rh{aLJNJYJl&m9S_@L0ZNpWM>B|e#m&b;v?A*GA zGSwJKiy|NtPmbJpJWURn50Y6ksw~+_Mp#`1lbmEl3hLEszV_=XG{2qg*OkDphL-z) z_*2_@S;=$uMRU2^Njl5K;j|)pXzi_1uy2~0Tku%n{RV>i`YI|TG{!Md7#(wvCA+IS zBF-%(P%?Ibyn;THBfW2xzUknZcQ_2nBVgd&gF4muC&vp?j}11~Fj0OTgO6j_DJS_0 z)1w4EJcaZ~k$_6QXaUlmLroknX+9r;iJp5kyfi18l&}o(eU9Sg>SouNqBXb9oH09V z`YT38=R{8NylicSkiC0%7~gZa^<~ewm>Bll^!UalkKfl*n{9{%XyLn+tAisxIW=>d za&^9uMeV>bU)J*d#6gQiu9f@RHH4OiR#Px<;s~|u6z@@;kr3}RmApf3hp23&cxL$C za%SDRCaJJZ4jub}^=j#kldrq-5%hiHw~UvG2CDHfFwp?PL@9+g zVg=b*c&T@^$2om{gZqsR-!S+R>8(@F)v5Aevs3)iC%fsx6Sd;4(EIzoUhbK4|MKF^ z3Z%nZrPTgaR@d)b6!sKDtfbsWfzI2&*WG2T7$>TLG+xZ%sOuRa6#VyAPh8Bw-p>yvFYUyG z8MgIx;teRXG&<*5z1$~iczJi0lxchsN!d8^|FHMwVQHOvzjxZEN!v^{PKicswbiIm zLvSA3#-WLz+ai*vXrl=#Dk?bQ)HFMB)}0-Vf{G0$pdc7W1SDioNgQJkjUpN~iWAN_ zYu?p;_I{t9bFOD!`#jfvhd&fo}DC+s`tJ8A2|2bBpUjS6v|o= zpz$0B&B{CmvAh}^kaGN?QKG|U&>QuHCHnVVR^!6G2crVsT`wqn3Ye#TMGMd=v^Ex= zVLQfa#L?X|{Ugn**L};x&F^q$%+*@WrO5aVQlwR``mumw9J4vGEiIr5XhJHq`)~+6Yjj*>bD)tyzPHx) zxgMymO8=^Ehkt^CkBbhUM*sN~-jOU#z z5bf2>fXF)Ad&d>Y%$O^_L2oKD+0JkPytddPCVV)cb}eaewUw-13H0byWgfPY=)uuf361F zE8-Ww&SLk8FH$>Tj#nj5O;Awk@@XXWJW2 zQ}32CsGi9@e{egwBg*w6i)|UxR3DnZy4qHnNMA%i!Eo?O3;0?Er@?gK5vtb{mszvm zDBy|iBMlSeAQqWf5MfWzrWwrmk7N^Xd*^*x|HUkUP@`J2ljMn{eXakXJA?FmJ8mK& z)kZ(Z;#O(o8f3)<8*$irY?|;Mpz?NH|7XvsF9nyN4h}yJsno2^pUbA#R85Hb>M*p~ zNok^_nQXNpyNz!f>-nMmqt}JP} z&LW44sd1r&fpZnBT_vGrsa{z)-xR#J*zPm@Ihos|E4E*~sWXO+R>Qc7HVk_!%3cg? zOWR8Cyp|$nKDnUYsFgvjTZ5iV&#i({L)N3p3$qG1*Vdo@vuple+=j>*n}5Je`C{k= z{U022_AjeT)nU}$I6KMWfg?xm{_?LJ`jhcR-_q8!`k3s}@<*bIInQg7H3t>r-U!a< zY^MkCgaNQ*_ONc0O=tHT{E6s-{tbI0A8O#%fTD$YX2a20dA8b=ghQ9czHe2f0sUW* zEm=3A;auqG3y<_7;#Azd)2NtKeKIEu<~}q=$QY3DPr!bBvs7N10Yx(zbsi|z+T8U7~F2#b*6 zOK;ccc!)(ozHG{S=gQkbjDX5Hn1*X%CC$2dn^&XdEv)%pfxa)6s$FJQUpC#Eq5X6p?X&DQ}P# zgp|qj(h`Wio~!njtOjuP^`(WXWXVG)%)F8!Keg33vy8yJZ#Ba%;>eYRohyr9SCMju z@h$DEr6b1{4VXX#sXyy3Huu8;lWGcqx+DI^)UYEQZY=~Kan?dz5qruoLa5)!=kp`; zH`0z{>Xn!DD=i+#TyvQ_Tg3wWRTlQ*q1@(I&1w zS+(Vv&`|J_E#37^dGv~F=7{BCGe|v09iLC;#W7$hIgfmg`Es>zM*LG*9*Usq6Nf$= zaD4ta`qaCglTB8v4Yv~t>!S};|LVCfB}tKN}NlV6)mn@UBWkDj69TJ z&rY10zY)LdbfvgP!3-N286x{~(G_`<8(;iX5*ieitk-MgZ=!RYYbhBKks3^|dmv#0 zT)3&gAcP>xZ?PY6^reK5%x%rKAG0d>F2}%%YD*CjbyBjxDaCeaB4_CM;z*+6Ynjd$ zCL2k4PUov*tovy|JqL^&?j9zRIE7zsx;zq#9>*j$+(3oIGCeT+cH7)g+AtiX-0QhY&v*CM4XVAC`te>SNJ)lH z>Hynp!55DO=HUb4zs;$Sj+^M}WIw0gO6)dvT$zKD-#hVph}AC9$hw~rRrnTh)Il#%|oE6RT=sU3E!0~>xSBL*5$Lot~l>x(^t zDS32BShJnUi4zV6B_+iwMq*wH(fV#ICnTL#K?NN9AaF;?#>ne$Jhw`N{&XM-x=NXv zQ#bT`V=A^2n^-_z32Gu3O7aB+nii6a11SJ^T~q=X93HJdV@6%DGjuSyQ`+5T9-cK2 zx)$V4Vkiw~5GGZ(dak=CRKD=rSIKs45rLbFI>61&aHr_2nJ9y?0slY0SV=D;7m;=y z9ln2@c3S)C|NJKkBujm55PQQTjGe{ zoFA=m(=KIPI#+GV%a;`Q2k4?qM+fffWd`0=TJs%LFHiGT5Q{G+gpkr^H$kaAr}jo9 zBUEo0+=a;kQnl9w_#1|we5=%|)Cy{O)8a>0jHQc~L}0t0Pe8u=R~}CX{za10Mc>4k zYtN78#m5XuIHP4TLk#)yn(gOVggC2~E-;YiL}%=vr|hQb&Dqu_YrDPpAEFK|9ydc~ z`3?19!$w~T4XO4Q81G>i?aBs36*)i(t43I6WMt~`20M*@`~6HmJ~?7%iU-fziQ-Tg z`?KRdB2ab0sBU%`q@*~0K&Li@9V_BgPy!Nlt%qldNG#)@K5uZd#;x^AG_>S7;51tc z;gZ3C{LHDoTKQ2iE;id$*_W9Gr1QJA=><^E6p9(8pfHC*OQGCed5&J>%oXCo?aDy3 zT5K}$0tj2fZ0$fW;Adu-nU(W2UA+>B0Uw%j+dJ*YPMJk=7&79pn?wzz_h2I|s4IG6 zY66%^?A5^Q(}LMh_<#Z!((2*q*kyO+FHstw;c<)1^AU@eG3Ty4>*(oe6Abelh1OM;s1W)%S5lWj6Xfp#1%}I$(Lw;q{ zVVdleq@Tp-U>O7DX`Vb3L16Bd=|B`FDBtP7F;xFj6aW8ir=O1hM*$C3h#(IoYu&hS zk4{s0axJNq-=WblrP}GUTQhv1yZfJ=V)r0HrW1)5CZDX zc>k(}F|Pu1bemF@b5VE7sTuY^P_o%DSnH@kH!c=4LTR-{ zu54=W343uj??N2sGiU);kZvjgFj&p_KY6t9=?;*l>|Av~3!36{W9)D%fy1^g)+F}U z*;9PdA2#C)n^YInKO6ve?p6MMPm2=@-Bf1E|V4#7qjtcv_1=ANx_~ab+5I~l0EgbTRTHHK-Dl2 z8_gy+EkoS(k;ZqnB~|;UuTL){7Knc3SVG?Pa5__OaxN*e(ba1}5o8->@Q(%k=}&9I zl>eY0`FwkMhL0C{clQY>O~v_vHxO|9QyH{T{axPGEd@4f%wQw#xrO=ew|)zoPIiA# zl!B-(d8i!vro`XO=ZB#|ts**g>{wV{CtSFc#;9!RE-q9!GZh8|fzH)VanJyBhl4Tz zuq^JsdKV)Vu$c_e2u=4}f=vg6OJ0zLUgEj@R0Y(eWheGx101;bjwidxm7{l0;bwy_ z8_aC#xqSloG=_hAJL<_5&>bw-d{)u>gUC+sS!HJ+Qg{6OOxd-Jz%NmIfZLAxs^+bE=M?IF@+A3Fioz z{w3L|#At`l;Wq@PJpIGL4(^(>9FBC}>6eZr{N;~3J=-uxI59Qnp}jY`rVK>n^&2xH z5mt2istURRY}#d41Em43pCtWsJoTeHrVUjPc(BvX9ZP z52HtRY#LIPfP}2w5lp3q#glHCf9r+Ro*(}H`TavJd{LfShw0z7TP$jNjY?XJSPvf> zP2;VqsvRA8x!a0#_$>5&Y8voy{-0NA^MpKgXGD-0hP3jb&K(f>Nb_n#g0U)lD1mfV|6 zFEpfNp3R`qXe1Ay_y!@~bk7|sswoFw?gQ0DO`(dod9lU$5P)Xh*C{xk_|;}2H#v-V zlhRiABo1|#wS=a#g?B4a;x;m83pXn?>x`jIzRP``{A|^HVM<)gz1+Z*K~tgq_2S*} zepFe3vlDSEZVTbq1@0J2K7TN^#n@pf@V51R`=JgRKl|@bp zU?;G6j+UPLN;V&Bt-^n>kfWpOFUGg2&?%^138?P;q7dITn-o0&>4YSmJdyO}KK;!` zxmyNDdIOzw?R7Z1ez@2|+YnHA5!wk*v-dE_AQ|Ye88EG|q~w`{Pi7FMRH`EO=%kGC3DpTgK2&g*hI%@q+uSgzFTvoUc^Lr$~5#C){ zG`kA@`Tw>AU07WBI_!yRcs65_&pK|LQ$DtmMNpkeM3dx7FbL3_v!xc8wuo%GUwfL5 z{`lnN^1Y0qmayawNwt52@>M~5jh~&kueYbv6XxsUQ){)7`!;tl9rH6vdly|jPbSdM zu^(dN9-dcG)&5sn#^3k(sC^lh92hA~OT3jl33G}n|9SW&3&Wmg$LbVt9E@O)6olub z<%1W{hSsiPLJcdTOvkN@Xuymz=_<|_IU&&~y+*zK7I%GsI%GT0IMh9GyYR%jahbjP z!kpU7t2V~4!>Y?>P}LwSuf?c=ChL6SOXp=N5VGt)9I%|kl2VKx`G4U|rD{11QW>^T zVoa zj64?8-UmoEmE2(EWF!-EvFik7&q-_{YXvyUme z9q}pD%bR=X=yFjq{pz{(rpa?7y%3Z<$dlXO-AA#4I6HO2;0pGqC|0nZZGF#^^)Mxv zlHlOkrY!k5Mue9hgH`Q3)hznu|DY-Sue<&cJacF)eCJfNQ{RQ!YM(k(cd^9Sm+c9I zDhaHJl%QH|oyHrA(OIjPVl|&-^QxcGuk5h=%Nb~fb7k2@Q0U}nCOH7BYwHPt4a$Wh z2+)%hYT(<&@^foRH$qt(3XKj&*5xuGf5ZHE2Ib1)NISbdlr9UV*u})S|4Ix98VPd$ zvD#D+gQ<8mwEKArpJmD>u>4qeAqo4txFRW*bYGqn#bEm>55P$|Sqwray;C+2Wlji_L zU`f-p(bcsHxb`$knp5mw{^7u})ut>C1NpKcfCH*IKQB_ifeoG{yLxu6yVx_}ko6S` zk($3U`SsV?dt(vAVz;sJ|h1-(Lm?^(Z)MOG{ZX}cs7)*)*WJg?4fWO;*O8-6J$+}C+sei)WGf61*+bOUQ#9%Y^nJo2yW7r+qt&tRfTC9?|<{E zE1MuL2-bkw2bD!-H@#$ADQF(&dqr(+PE?L#?NO*wF4p?eV4tZ0R_D@-H((uKHpCrQ ztlZl!Qg%-)8O?$N)=0TQ(c+J0w|wg=-cNJx*yp7bpWcYV^k&6AN%o1OWMmqU{63@S zKRz5iZIc$dVCRZi=sa3t6y)B{4zzA_x1V#GE<^U0ors9Ae@jXl4=Ua}WF_%W^R*w4 z4=49lhe3Ijp_Wlf6SG!$WY(ZpfL@7{Dw+~X&EI_ZWg%gsF)mj${pz=Ci|VIO4P2Ju?nUa)Ztj4nZzPQ!2LjIfVr{;%J%|6c$9+ZFit)cZNC-)c7&dTm}K2AN#&#JNm;BJEuoarSTTx6jX*@Sap{QY&D2c*EztTOn_EX;MW~ zcK`+Z!dImAR}JaKV+84*fVP-aoFogm50q#L+6pagaDPwHSuJ=jpJQ7vuluvjhPj?V z7<80YNy34*ycM5n5acJGOSGW8(8|Jsh@L?YyZxEC0c_c?)-_woNgP7!gs$8dn5r{b zd`q9~>oz%UK^q_4N=<1=|G2N=z$b;{nAder)^+O|je8aSP-e*IDEmgaJtt##`cC-! zz>!uyPE~tY%tdh+!{o#@kz#v5sv%5C@8QC$?c8xv%xE?X;}M${lZGL4*muf?jy* zuNSMS8iOoQ#2rtkB(81@p-8$E_Zg68lQd5pL0XHf-F5FRLB-wY8-xtl!<7(+PpL0o z)?@@sdBJCmuGTpMVFX+|F(h3I1^~8@wTbF4r^nqD!18A6S-P8GGUHay3v_M@w{VSu zs{ZxbnkC_lVY)4(t@=_2KhX6}Xc_YMZAAz&?{G8|g{Hror;cEKZl&$q3zf;#v7zyO zfq`>1*eX{zq;-az>-U6~5bG!}FO0BSb_p;92{tEfTtPNhMhQ++mLCqBp@yLJLe*D| z)5-;P&Z1pRHIeZkY;AgZzPBM&ojyem_DR)Q1xEB=^X_hCa#FrLJK$}j*O0G==rqfKDhmvN>IyM_;N$p$l_`3F#lkSQeEXg@9Ef!}kJ1Lr+^D=iQ1^v^dEo%jkF814r}D zxTomaeEU@->!JPjSTfDDGx5G0sYaN8+Ci9%5De3EhwP6JCBN527R6H$r$zNZIP^#@V~V#Q6o$E=+HCO$Qh89KNvlXH59!Wu9ay+uCBsTo&59S6!lwI(%)34J5O9T@j%NmkV5B_w+7IF2SvsBdhf{63fU{?(>4CkhP?Z< zcRwgxgv9IKuu^UXiB)JB0`ja2)wmWl(%?4r?y9PZ{BBpC29f6Z4AxIM`!@7R;i*^L0Drd)QB75B&gx7 zPcfzoXxdqY_1odTEPQ}HzaR!6yKiz+l@N51X^sNen@f-l6+^R9Q*EHf>(#vWD;jk(mK-fj-4?=HRZ z%5|EWn+bmJhk5dsgR?QSvt@x7fCM*gx3=ScIr=|V3UKygti?@3_Qo-O>a#?=ZsL)d z(VWTMd5Wo3FZiv$+irx3^ah9$>3@gStMMxPZxT%8Cn5F!tOxOV9NE7%m$hu>Z*wiY zK<|$$=jN7Sy&CQR1Bv$T;N@@r*>V2qHZ6hj7)@G3C_%cw_MefyoB8OI z=&YH7=cJsirlCU<4V9tlw=M`s;WNY=N>DFGXD19fJ4Yjix5AXMll}C%GDM;N!4+-4 zJPlpTm^ZGN$xs{PDK}kAG16SFVQ8*T)l)%B`)C0K;7iYT_o^s6*s@Yo63TU`y!=IB#soO`200}980S{CNyOD$=6`R#)j%l!1ywuWPx z^W&N-N;f8hy^-_HiDr&KQa^pt#iuaB%bc{(gu#ZIbJ&}2h1^+iV3PmhO%e>p5 za~>M^%sen`8p(Hpuc>1dF!0gXj0Bl!_w|720<$?kH)ZTZbcbx1w;gQQ9bAxYr37 zO|1H~%y8Eu)VCM9$Cc@)Ppb!NeLPi&rD>yCX#@+@lZKQp5*c7e$IZk%lapVsTALr= z8rIM;wVV7cWH36|2YZ1ol7B`C0AlTJY)moOa~+P1LAV>(?2^&@CYHw%;ko&Hwy}H{ zWLN05;GLfs!lv=A)2ADocTV-64`y14DI9X<6ukh}&a47N6IxJN#J5z`3HRL0GyZ+? z1GFgjfH0X)^t70mo!B6JG%-++s;Dg?8o@DKPh2$}gfYk@TR-8_2SG!s>SVqv^3j__ z=RUn7F5KT1G}P%<+wkFZzD7H{h%Beyh1%hD1M^oYx`g?nNz*Ra^UQ=2jnnZt&n51W z%MKZ8nqD^(VNa=-h4u3lv!l#D31w2uGTM->53!&igaNe_*RGS_HFqdDO3-CK?xy#p z+(_}yYJM46tom%)a`<)WxF}^mNlXi&z2Dvr$clmBC`Gv}<3Z9$l`kpNP8(!>X^E3F z=|ylR(BIvFQ_*i@XNip7yHHb+vX~%Sh!G;eXLf*l?kcI;JH$oJw)4q3_b+ks<4UOD zA&Nv)hq-=*YGT+uP``~qm_b$bizbn#xJhLXFjztUn3Jq%ZGClG1Q5C^c60nj=v8Q| z0<ac+d2lWcZi-uqp=eueTCHApt`T-TQmN(!OQM$zbq<{yEe0Huki_VpdtEF^ljdyq zXlh-d%{{m;dwU_7@t1)$BfaQg{GFkJ$Bd~E4%2A(TxVjLPi$AaZss%z*-Rl6{R!&Q zNoZknQ40la>i5mN;_!QI>xHUdhFH4aZJ0TpNiDQoD7`W{IQNYz_?KZQ=|beK&?w`n zW0r9Zou{~W12PC?$+jU)@|Ic_fv9%!1qK2--6hRPN%QGjFBpK^ ztIu)OhqQkuhurF&oyTF4WxEWxV5CU0!xUx$a#PQab zP04}gV~6 zrn|oTW5_bwLGlK!dahy)rYI^)v0Cwzq)nlg3~wxkC5ubx3mPx7Zl;uE+nIQn{ zD0#C1A7rm`7xL;nfu@(=1!}Kd`*IX0E+74uA#eMEu8tC;Rsx_7ew;ZKem$^D8IYY_ z@Tg(O;Y{wbh%^ENS@*X3eYv!e~A#hf9$@q{r(id^XdqcX3t9|JNfM3C6DrP1f z8M9D7zZ}{0pf<9hJ(5z{#zn@R8?Eb^BNqb}BY<8KWF5c+R5{S#fu0WsF624H36$be z2x(qUJ}Aa<6REH}G3Rtm%5Y#$rXkUGSz#ib-g?Xi+iX1+bi&g0LHF=j+ncAaBMqxL z!>k-fIdfoS&X-w9T!44fmBPRsHKI?xu)g$G@^&|Aw11z5CzGsdi zrEcKR;c{CIw`${V*NrK7;DXp!TcRqz@-pYpdIMxeW1!3nlQ1fJ_oz)&kd~@SmpLG-Ys?0di!wvfmmFQbN;+=v4r7G4y~BqLsT%tX;7m1s zXd@swoCaZ`pI!72F{lJfY8JL9oqP&ZH0?aoF_eP&c0Q>pvEV8%K*lCda`Sr(Dgf9! zpw|WjBnJ3Thjd+E5~iIl&MloNe6hz$^`}%YOTDHF0;*optguBq)0i#6*`>{|6nZWm zQ;nA`WG5>AI2yloWxFBUP&j;Hh8S_Rjjk=b(g&0{J5vR%$V6#}{i2|Z+8rxMaH2-`<~wOG3vuR6qI=xTC}~ju{8h%d9E(4KY4prN~G4Yn7kK^ za(48&u#(cl5z-r?DxW%tNQK6AYN|qD4xZK2)rP+do@}mzar}1PTqE8|4WM5^c{j zh~2jy$o9XPd&@bVb(1p4%!(03oKVXGo@8J!SjiCxh1op;m?#m?{W5PTlE$T<{=$we z>W>yyCrCfK5%tSI?VtYJHy(5q@NTyle>0C1H*AJjFwv~@HH(rLb=Y8bX%6N|gIH-@ zU{#cx>^~8M=hcCZ_Vk&`E?(;5QD&R0P9+qHy0~w*RA?-$C~6eA2I<70k^8;>Nbb4L zM9q;4Kg0hphVWbLwVh#lL{#Q5e|GflY-qQFDZ3jZg7X@fy0*UD*(r;jzWGFrwHu_o z?3&N6*#Bkk^CsR?wH34CxBi}qKMi*Z86oRa0V0i3wh9+rXUE%;x~w&Su_=ODcdNos z`^$AyxYE`H>_wL_5>0C&6LA+nm_Y%7mZ+aQht%Ytv`NXB_n(C*)O@DRwS~BPsRs-CDQ98!{!)zso;xup-pMmZB#(_Fy`T7aMnG)BU$7 zSCfd~fw)`^{txofA0ioU{$=NcP&0%@0yO1G?xSv?GT7d`m{Qfowwb|FEpy$sBl*6p zs-J_`D_+*c&CNS@iZES%1{LIBa^N%56R;>J@op=2+TOlj zJ;HM8zyVn5K6wQfs~uEpFuOW=W=ii*r*@wAfae}+%0`R4jpc)<)kA-%yXQDd%=Lqk z80Oju11bTTwBmwJ$5y*5Ku;N0l-vJQ2$a}R0!-XRlM0It-z9|))|uz-_RfiS#9=eU zg<@dFefBO~qcf}R+EPJ0PHHT}UR_=c|I)qFhhG z&Sfh{pw8ar$x{NX#G-;|`0l>V{TZHPum3^1Zr1BEMYsO*4M+FCc0<{lJ8=i1CVpH< z9wGw6Icv!f6QJBn+r7}l5Hetf1=2Z_0QD#(JNA6z<6aQjbYN#9u;zBdos29G3V-y} zC)XYy_!vA9aqw~ctDDp-)!u7*CT?7~*Giy@iM)bl7o+IzS9a8KRr>DIEe)}!A32z5 zmJ!HBXZ1mq+iDDW)U~s(gy0(aIeP`lPbYv`AasI z7+}cMpKXP8G$!Ck{)a8rL2sWAJ)e754o$9G{{5704?J@w?gWkNXnq?VKQ?-@+PAOn z@xly;k%}z61#*hh0~ky&kmdtqP`8ck($sF{G75s5Ah1l(b*aJ#--ciHetYR z@E`Brmsn31)-=Z6VE9B%A%_$C(U~$~N}qQdxnj{BE42&RnWflC6@>Unn!!+fFGx8C zC~q3zkfqV>05SThcum+}H5E~+?!>||{7R`^aoP@97NJrtiDN2GGZ59Uei&{(0gYY^ z>^wL0;^$e)a0s(>di4)_FZ%P|G-$QQg!64UsQdi*KGgqHb6VaqO;sk<0cPgiSz{-x z&V&S1uJ3;Jkts1M$z|;L%kldf<`(9~_V@;fFwjObAUn%%4c^h{Q0@syY@c9nD=*C3 zh=zxJilKc`Tb;s)3kCwN!n@p;0JDEo>i)FpqPxN`H5sL$)YSWG@!6kdL*|e+yN`d{ z(onIS0N7L!L1MnUSlm|=;4sbxv{uHW=rF4E*8QvXAv;X{&!?aJT zqgXARp%oEwWhh0ttg0>#bCzO@v*oVZpC%0^fAn?Wk)UH*Yn8qKSa_WR*FdAn&R|n= z?mVxaMSjlvvJYFg2ev6AMIaI@r!DNOy(>w+JN$^`3ro1$`}y3Tcs0{fGB1g?lE^RR zrWw~e0uN$*Gjbm@kG}eJY#uRYBz(Bm(^VPtNm`BnpIL7AgbwEqiNhR|BHn3HjZ+GW z-rMmgUf$8w}xmDH!WCwr`5xE%iW9fwe#K=A2=Vw!Gn*C~}jEXl7PY}hY664Xa zjpaY?Z@E-g%xro$9&#pB2727hD!|Y!7>M6Z(dGFq`!0{ckWk(^q1xi*2aEf6inG-1 z*Mm-&0lLT+v&4!Qoow^Pq5W(`dkE8R3#g2^*yM@?K1u8bwmR6G&^t^!MT3e$dfdg$5zJ zypSvV%8_}P$a|Mw3Zsu!%@gYkDGSi~;uI^yH_q)z+$u7?LUD(*l&Z!Wwhx#s~ z#NI#fm)SYlI*dqFdgd@x4n;B6-3X$M3WuWE+%Ou5AWv6 zXeJ(}2(^|P3Gijt&78PW5H^&2+@4}#Uf9F~HLp%%;LW6oX%`qL#vo%>&TU?icJSN1 zAgAW9F|LsV`a-JRTXWmg`qgxeoQZ3@->Dx<%Q6sp$`*)1Q1Ki6dnpSN+MchAIq9-lCStX*zQE7#1}8j*{i z#IN$B=3-_6@)KT!o$P6w|DKqs&w_Zk9|U_ina^bEzx(Ad>+wH(2D$Q1?&ZhuGiY1l zBVmXORu~sWm zC;;`h+Wbv(+8#AI%sJlu8oc2=Es9!`=s{|{oB`Mj(i=#<@xl8F3SuxwLGi)~ilOGq zRmW}1#nQ#Rnq{zDa$)Mmc2td-hWnu`OT+>kq~wTDHDKn}_na-x_t3PuU2*_nPuMf0 zO?LnREz@IqsxXO7CbxcOy>@#ft_D4T-~a_cI^hU?W@bPUjfC^;vG?qR9O-rFIx%Yd z-T0m27k(x)+t3c1%#Bi0nad}dVTeG3>h;(KSU(X>W=}do93gN}PYGn@*r7vLwjMS| zj5`ONjyeA2#9YEQO4R$fCLpo~0I-adBj-#dOxcJp_n`}bO^2*fY)U#EA)-z*OVlVK z9H?+x%8OYziN=#q<*c*4bE2B?Q8xUgaM6c{&HxCySDm8p2||rhbU=VMs4v{kE*90 ziIHi9s(QUKQ%@dH1*^PAU?y#+^zt`#ftOw*@IyP`+MVWdwGv$w?zp=BlSeeRb#zCVO&> zya(`on%(osk(kXSeeUhtO6_wa%2UQa;;jPB{U9z%WdWZ2{F=C12l z+tk>)o9B3MQ*>3S4YvhXZ05@{72*mlr6O8DLVN=DdMRKpUw!l~W6b9A$WyZHiL5Wj zuVfB%(|qg0frf>92ycDBCNnGF)$mLHWdM1bSAYDQur94+SyEN>xp9$k*Nd0txg0s`UNIp4ko&0?QnJI%=<1Nf%AgK#WoO6Qs4mwOW? zpEVYqiBGPKoJ+XMwX|A>#x4#PWxZw1kYX2o2pQ=>3~wC*X<5xEI`m^?4`tv-y9_?a z0>x;_NsM~u1~o%ldr^!1ob8=5Xx3{UPB_y+dSSoF5!Rj%H&No&dzA~_0lHAS`fLdN z%tZc5kyQM}_s^>@OrYkwX3{toJqjiZRYn#-qAJ-q#~5VfPt+^b7^&m}^yxC=Xh%nP zW0)=FTKvrtQmF0&Zl$R4cp*G)W=KA9W0XuxXyp=?;UI+nGcy<_cy@KsDQ0D{{j*j3 zm}6sUZ}LLl4i#MQ%H~`MN3Y9P7h- zwaCBjN9Gn*OVUYEPy|y1e?`$n1&~;0mp^%O<1kVGndY}4&GG$~#S!22#n`df;o}22 zr8xogzQEyzJdDT-h}GC#%4jfy_2fg%Vj-RI4#d--LPFv*%-1sBGqxpzZJI69a_o9D zap_qD0l;RWhG}wc0g{r*hV(jm&&mN8;BXNosKB@0^rQQ>h}8>CuWml6;~6G@CR*=C zK@pKm|8A<km(%__03Cx*Kf?m%8&V57*AEw zuV2l`g5ASro}KlUiu)*8(J)0@HgNAUzz>syKAH~YXx?S7LH5|y@4UN(W{}XLgrb4K zQq`(qN(eJ!1CV`x8r{}!LdcPRm9~z<4lrS?G3;S$7IH|Z%!-UnGTn3-l(SRSf*Rt# zOWOP7`#0u|X;c~GlS3maghv5GFJ82@-;)(c!mmYI;*J8(V-0DsT|28pxXi{T`Ob*^vEMRf=qHwHdKQ;G~*F-Mk7ekJbt#HH`}7+&Us5s`e* zfS{eC8(58&**7-LFS%PTwYUd#Z50lC^@x-Fd&c0?+&JjSsW=lcrHDZ3Mj3IKPPoBA zW$&!~&UIW-`AEq9wYOr2bj~5?{!ts;VV^g(sY8J`hB9ZQ8ncy4f4e;f1U@@EajW#C zrTWv-X9b+5)JBs*Q7r?Jclv4q3u{n=s?4BCnwSZI)}Dv#V06?m+EE`}_%B}6|NgW8 zm#@ITt4zY*Ph0cMFjp-_V|SM}o@Kc{%f(hM!2o#A*4HP)y7PDSC2(i+2bBZAO?Ix# zq7_692)=4ye@pU)SF$v)wMz@Pa9m>!ui3*w^V}UY!+f#|x%n{$MVVXhkqkgu>&Z`C z>e=ghAzQ4&h$3*Za98(CJ-WpBEu9U(i=E%&$knedq<2QK9v2AT&GcJtu0&Y9Q(zM6* zw4Nk$U&PFEN3qg{9Q2~Uq5b?Yz%>lcm7BsA9kJ<~S`Kb+|x=QYwC1(qBO# z#+B1YjciyoRtyh-_UVFng7m+rar0h4se3x{j%}~gw(ZzWGng1|yEP2ZccY*VfAzXM z8)EHN`XHof$o_6xsOpK6!`^i$iESmhXwlKGq44vxY@-U6)^Jr5#P(XaQ`7$Yk7@`1 z%gyosWB_v0`iJQ5vBNHNi0CfYKdq(WS{>Na`0yb#*Kzg30X|y{P2X9FiLWd?(F>-= zn*7L{AiUUnQFm6n-4ZD2dRncnTa{&{QbEM`&q^Jk$}R5pxH&X17q*gd1ofZ)1N#_+ z6U~4d%Lyi@6KS+cfaE<&_)F@=%{qLy6D{Hj*Qp~$U>!;ZrfKr<*Y)CtCXJ zlPZb9uLOi#xJwG)2h*m<2rQ*g4`8NIs`1;j;BPfX;G_K5tXPK7YycVK2!n#oOre5! z3P*QFo}j1fi<@gB&0UT#&|I9NcDP4zVTCAQKU~3mHtSg%;)s9`&wtieF$AGCFfFk0Bo-GjE?27cv)~{Ztc#YdJYsIfw zrKMxVEmeDd>3mgebaQ)fiQ)Rz_FQs1F`zgK1oQ*|XXeSbyz-_RR!U!Q97ehLevO>x zpzhItkq&q`%|!#lnbCCGM{8JY)Y9_8jj8KCH340U*hw)^5ECf2ZUyA?w6$$7Y!>^c z+Z;)P-*fI5**?VgIX>UYtfea7js_&5&sZ|M zk`*m#4`o3dMP_>u-umfM+ilMEqpdSmQ+J{Y-Nw7w_3=*haoL_?xKtvvVR$EJ7Z0++ z227F6N}f{`R1sYvqbecy1i3Cj$c3fX4NH5#_Q z;v&4~#k8!L*4SAZnw~F5k&>@uq)*i%Pi%vRraNBjcB%eJR3nGJU|ts%(k3T_o>w!- zK=o>KPOmal$#K9AP1Z$msQE5uf4Lu@#+c=X{)`D1Jh;~82UKa?VmHOFx?A#6M8r9k zi^&P=%JkhTrGX8<_Y#{d!#Iov4(#CF^|FM%HNfl~VJ_MyV_3HegN%L2R*o*l?rX{* z#WNHeWjZban33Lamhre3V%r|wwn9+$ol9?4zyrUS#M#bHI=XnL6P7&SAkEKyi9sHX z>z*ICaPBJR#4&c*)(r9QX$9t93RLBS@l{rAC20@l#7Pj@PE}^m%R;B3XU3+M$Aess zJ^L*zQFTRrG_h$rC*E8eH5Fd0s?CwXAjvTv?S`3&s7jWC0{DDMJyP|OnCPr|^S7C| zn`)TSfb1||-^3^X_Q)_|F-+h_)V@i6`*%_U|#hS!+xK)u&h`25Kx*S%e3b zbb7Oud*NoiieON;RZHheQAQD=>7Y~ZA{hWeVBV@ z3Nx)RO5DSgHgSzc2;#P9IxUi78&%?#X;hF9RU#rn)vYC@M5Kf$8UzvdJ3XKIp0)Ox z?{DqjZ~e~i>~q%nt@X_x79q*UC!c&iulMWqd_JC2Wlaenfim5KH@ux()Hu|W^v2?u zKFx0bV4R9y+Sw!cPchNa+z1|U3BiT{fr8c`6ZOT+wW|mJ_g9@i{pQ~Vp!(BO{!f1E z!1*LK-_-H*s;w$7#TUW1*qm;?V?ADA@BTqqZfN_1#^LpUoF@Oj1H}GMZpwc>{NJz# z{#`@Ge{RZA5tbMZ?u)Ex#@MTEQ3J9nF3rM zx=$9}(<+uiobz(!H6LAyn^T)0Cd~geC)&ey^YiVsrlWHf{5#yI$o1&H^~)0^Y})r3 zPtT{EPmwua|NB2*^MCl8Ld;i(mzLAzA)C!g<3C~ymoBLIe7>txNruhM%*-w(7)`%? z$pkae)c~*^(6y4ZiZZ2ahAbeMDi;XSFcXs>b}p^J5JQ)zhl7X|e!#B-$`Oi6>ygNu5*!0R!kj8BQ!LxCh zuP9(fv|wwfK%qm10Uc^vov*~JsT}$aYF_(X#Gq=yIeVMAvNC0w0K554B&^arulwV3Ms9XNY>fzP#&`4^O4e=#sk6$q zaWY7?z&YddR_Zi;p`8mq)UC6mAvXkT$V2ad#aTsk#FWVpnc>9wMGJ@`bBVBdTL`9p zgq%Yjy8uf|Pp_OI|al2nqK2#mCtb8x? z8O8#VK|)|r+tB16X$uch4cv%XOmI0$4lRN36a+*VK2ypA`kVMn{NGD|H5}$w5F1HxftC7bTv7QitusT#8woT0DgN1q+OM4?(vss%kNnQ zwtcsU-0Xe%ZOeq83l(~fY>$1*>!5x`WPBE|4jDJ$uuM$;qLUl^Bd2TR>6Lh}ix%!e z5jyf^2fCwd%WG=bvWjnj&M*ZcDTGWh^ z>EKu04I#xk3pa|1;j0DU2`!zFBqvC}^+r*Rx*{feo#{HKBNQ;DiA%PWZzm7R9SRC^ zt9_;rzU1SVbkMSS?6RjBFVK~%Uf>Cg1((>T8}6DMjmln@E?E((?o=uJ403md9CG15 zQj~i7>xRoPk_4?tMt^!7sfegY(tW05u=djI?d?8%c2C)1Mh~-8ck8M#5SLYqc7rAs zMv|F{mUT{G%erpPD$?iBhG?r_T)iOpeDqFi$uf4*Y^I>ZXYX$NmVL)XIa65nRg0;T zLLwkpRdC&=08r`fjnpzG!{TIh7@43kHujVjZETSv&IKG;CCvz~Z~^Sa{=>&WQ0VS% zxJzmdS{V>KcC=HLUT*f3xlyd*rWX23KPK5p6kITRCD0#Mq-^#Mn_ZWIH!oyT!&1KK zdd0}|9&gOQ>;1RDg~{qcatTWSh>QnY8Uc*)dYZ%Q*NV4hHH$Te1sMT+-OzbabD?2N}|3gTDx5SI5*k&l&-R>er+VZmXDZPN^9kL zx^_b}j@)D3{e(D}8QY#&-Q~QoWnLZV<=@$e=p%Ov$Tb~4{vpK@cjKu`^&Maj0En6O z_ZS`SR0%3Y!g5TAHVo>ms#t2))|NNw=G>6TO99*ha3cZJp8uGpZSLiF8`58t;cC~< zC*0!0uML}2X&nhNiM{9zlwT=o>0a02T^}x`@ z(Nfp5!u=&FoOutn(szRw$B8y~_eOy2_ex5u9(H#p*nhS6Lt&rMCkTW}IAiS2=C-Xj z^=1&u9Ml>!&H^oIwL@9{q-k&X!woEB1E2ZPx3BHSY_3?wQ*85peRxws;m=~Fp%JKL zV~tUck=Sgoh4ij3ouVF$Nx^(Vitx1=D#@aVlgw_-Ecnv%b1}JfjEL3{pfQgL0Su+8 zJMU$_33_NFlpH+jpP3re=P`$4w7uBHMujjDvY))z5K$7xGf@{8k^c61u$#kgD(_`nf-e5F5GA;dUKE`qA34d#h&$Crc4?pB%ZRgJT^$lOCX20N?2^6`Y!?7g* zfP@hZc`EdH$eV#o$3$vNqM^STSD4!IWVK`bW;K3(v4pyUMEBHb2grZLBl0f2BQ4p)KvweUh*I^dUr3PyJLsk?Ge$dTMaZCcR3I%PvO=pkd6YL) zL^oAq^t@c)6xh+?Kfj|%tp6Be!!HBcV#Zfk*F9+92iey>UgGx6{j#!FktSfXVOO4< zSC3hrb>;$x;;xr+p^WPu9b?ZN2}qd|qOV{MsFY+!_m@o9?U=Vdj@xOKp5qVz-xR$A z#LM@=jjkupdqkHlkR<9-M!a20iE4~*e;2)S*SCmxdtId`3g4p=4g0tQ3wl zoS->*fE4;LTH4RFg=g;ijjV4EYTJSa$gNQk7x+lTpnQKd0II@^96k|Ujut{c#9Wu8 zE+mJCx7l#`E8zW#^N{CDZaU!hv}O^6=LISUG_UWtCJwuLW#xa7-2%UT^!T~#r()YP z{+z}_3Rp0Ha1U)8?UA<9J=m&~4ATQ}+;uZW_?4`8lD(QPD6LmkbJ@;vYD@~LTt;j0 zCTe*KlQh7o0N`|8ySo8EB-aa-l@qtq-4{yv3&GZf{rE+v(7VwxS5ezjV8x%6madzL z8}bXC+^y(PRA5hak!XmF;o7&gvhr0<;j(k@Wk;Tu?O|G6_5cI460QOl`0pJ9BXC$LeWjgm&g6Zr7x%dP>MqJXNE)V)!@)!hxudr82QKP0r|-b}8u z#;LU!KW0k{va42I*OLkIUNEBUZXkw1!BDVbFqJ`Gl*uNA>2#8?SNyfN)u Vd$r&+_qly{F9DPF23q&)sY= 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 Date: Wed, 21 Jan 2026 17:09:41 +0100 Subject: [PATCH 060/112] Move the scenarios figures --- .../figures/scenarios/scenario 1_small.png | Bin .../docs => docs}/figures/scenarios/scenario_1.png | Bin .../figures/scenarios/scenario_1_tiny.png | Bin .../docs => docs}/figures/scenarios/three_nets.png | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename {netsecgame/docs => docs}/figures/scenarios/scenario 1_small.png (100%) rename {netsecgame/docs => docs}/figures/scenarios/scenario_1.png (100%) rename {netsecgame/docs => docs}/figures/scenarios/scenario_1_tiny.png (100%) rename {netsecgame/docs => docs}/figures/scenarios/three_nets.png (100%) diff --git a/netsecgame/docs/figures/scenarios/scenario 1_small.png b/docs/figures/scenarios/scenario 1_small.png similarity index 100% rename from netsecgame/docs/figures/scenarios/scenario 1_small.png rename to docs/figures/scenarios/scenario 1_small.png diff --git a/netsecgame/docs/figures/scenarios/scenario_1.png b/docs/figures/scenarios/scenario_1.png similarity index 100% rename from netsecgame/docs/figures/scenarios/scenario_1.png rename to docs/figures/scenarios/scenario_1.png diff --git a/netsecgame/docs/figures/scenarios/scenario_1_tiny.png b/docs/figures/scenarios/scenario_1_tiny.png similarity index 100% rename from netsecgame/docs/figures/scenarios/scenario_1_tiny.png rename to docs/figures/scenarios/scenario_1_tiny.png diff --git a/netsecgame/docs/figures/scenarios/three_nets.png b/docs/figures/scenarios/three_nets.png similarity index 100% rename from netsecgame/docs/figures/scenarios/three_nets.png rename to docs/figures/scenarios/three_nets.png From 6e229379fe9411c59d375888eb4524cb554a8be1 Mon Sep 17 00:00:00 2001 From: Ondrej Lukas Date: Wed, 21 Jan 2026 17:44:24 +0100 Subject: [PATCH 061/112] Update docs --- docs/configuration.md | 38 +++++++++++++++++- ...nario 1_small.png => scenario_1_small.png} | Bin docs/game_coordinator.md | 5 ++- 3 files changed, 40 insertions(+), 3 deletions(-) rename docs/figures/scenarios/{scenario 1_small.png => scenario_1_small.png} (100%) diff --git a/docs/configuration.md b/docs/configuration.md index b1914a04..e9d64f7e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,7 +15,43 @@ The environment part defines the properties of the environment for the task (see - `save_tajectories` - if `True`, interaction of the agents is serialized and stored in a file - `use_dynamic_addresses` - if `True`, the network and IP addresses defined in `scenario` are randomly changed at the beginning of an episode (the network topology is kept as defined in the `scenario`. Relations between networks are kept, IPs inside networks are chosen at random based on the network IP and mask). The change also depend on the input from the agents: - +### Available topologies +There are 5 topologies available in NSG: +