Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f9f1e8e
Add check for all_action being None
ondrej-lukas Oct 1, 2025
b73d99b
Fix correct return value
ondrej-lukas Oct 1, 2025
7c7e508
Fix return type
ondrej-lukas Oct 1, 2025
946561e
Add optional type
ondrej-lukas Oct 1, 2025
867cb5e
Add typing
ondrej-lukas Oct 1, 2025
9d90a35
Add optional typing
ondrej-lukas Oct 1, 2025
15dedf7
Add default option
ondrej-lukas Oct 1, 2025
627a6dc
Initialize empty array
ondrej-lukas Oct 1, 2025
1883a0c
Add return value
ondrej-lukas Oct 1, 2025
44b0be7
Refacord view processing to be more reusable
ondrej-lukas Oct 1, 2025
53349f1
Split IP mapping generation
ondrej-lukas Oct 1, 2025
cdad6e3
Update mapping of the start view and win conditions
ondrej-lukas Oct 1, 2025
4c461dc
fix goal description mapping
ondrej-lukas Oct 1, 2025
f222d3d
add generation of win_state to registration method
ondrej-lukas Oct 1, 2025
4a56a7f
Add generation of win_state
ondrej-lukas Oct 1, 2025
a8931a2
process return tuple
ondrej-lukas Oct 1, 2025
cac9ead
Generate goal state at registration
ondrej-lukas Oct 1, 2025
168a70f
register the goal state
ondrej-lukas Oct 1, 2025
b95fdd0
Add note
ondrej-lukas Oct 1, 2025
39c5567
Add goal state to agent reset method
ondrej-lukas Oct 1, 2025
f69ba5f
Add processing of the goal state per reset
ondrej-lukas Oct 1, 2025
f24dd00
remove goal_state when removing the agent from the game
ondrej-lukas Oct 1, 2025
f1953c2
forward the created goal state
ondrej-lukas Oct 1, 2025
e5ad8dd
fix parsing hosts, data, and services from view
ondrej-lukas Oct 1, 2025
56c9eab
fix data selection from view
ondrej-lukas Oct 1, 2025
2a6cecd
Add keyword scope to enable better definition in get_data_from_view
ondrej-lukas Oct 1, 2025
1294c2a
Fix docstring
ondrej-lukas Oct 1, 2025
d36086f
add note
ondrej-lukas Oct 1, 2025
2ee09f2
Use target goal state for goal check
ondrej-lukas Oct 1, 2025
93b1642
add missing Moc value
ondrej-lukas Oct 20, 2025
fdfcc65
remove unused imports
ondrej-lukas Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions AIDojoCoordinator/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ def __init__(self, game_host: str, game_port: int, service_host:str, service_por
self._agent_starting_position = {}
# current state per agent_addr (GameState)
self._agent_states = {}
# goal state per agent_addr (GameState)
self._agent_goal_states = {}
# last action played by agent (Action)
self._agent_last_action = {}
# False positives per agent (due to added blocks)
Expand Down Expand Up @@ -462,11 +464,11 @@ async def _process_join_game_action(self, agent_addr: tuple, action: Action)->No
agent_role = action.parameters["agent_info"].role
if agent_role in self.ALLOWED_ROLES:
# add agent to the world
new_agent_game_state = await self.register_agent(agent_addr, agent_role, self._starting_positions_per_role[agent_role])
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
async with self._agents_lock:
self.agents[agent_addr] = (agent_name, agent_role)
observation = self._initialize_new_player(agent_addr, new_agent_game_state)
observation = self._initialize_new_player(agent_addr, new_agent_game_state, new_agent_goal_state)
self._agent_observations[agent_addr] = observation
#if len(self.agents) == self._min_required_players:
if sum(1 for v in self._agent_status.values() if v == AgentStatus.PlayingWithTimeout) >= self._min_required_players:
Expand Down Expand Up @@ -720,10 +722,13 @@ async def _reset_game(self):
async with self._agents_lock:
self._store_trajectory_to_file(agent)
self.logger.debug(f"Resetting agent {agent}")
new_state = await self.reset_agent(agent, self.agents[agent][1], self._agent_starting_position[agent])
agent_role = self.agents[agent][1]
# reset the agent in the world
new_state, new_goal_state = await self.reset_agent(agent, agent_role, self._starting_positions_per_role[agent_role], self._win_conditions_per_role[agent_role])
new_observation = Observation(new_state, 0, False, {})
async with self._agents_lock:
self._agent_states[agent] = new_state
self._agent_goal_states[agent] = new_goal_state
self._agent_observations[agent] = new_observation
self._episode_ends[agent] = False
self._reset_requests[agent] = False
Expand All @@ -741,7 +746,7 @@ async def _reset_game(self):
self._reset_done_condition.notify_all()
self.logger.info("\tReset game task stopped.")

def _initialize_new_player(self, agent_addr:tuple, agent_current_state:GameState) -> Observation:
def _initialize_new_player(self, agent_addr:tuple, agent_current_state:GameState, agent_current_goal_state:GameState) -> Observation:
"""
Method to initialize new player upon joining the game.
Returns initial observation for the agent based on the agent's role
Expand All @@ -753,6 +758,8 @@ def _initialize_new_player(self, agent_addr:tuple, agent_current_state:GameState
self._episode_ends[agent_addr] = False
self._agent_starting_position[agent_addr] = self._starting_positions_per_role[agent_role]
self._agent_states[agent_addr] = agent_current_state
self._agent_goal_states[agent_addr] = agent_current_goal_state
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":
Expand All @@ -764,7 +771,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)->GameState:
async def register_agent(self, agent_id:tuple, agent_role:str, 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.
"""
Expand All @@ -775,8 +782,8 @@ async def remove_agent(self, agent_id:tuple, agent_state:GameState)->bool:
Domain specific method of the environment. Creates the initial state of the agent.
"""
raise NotImplementedError
async def reset_agent(self, agent_id:tuple, agent_role:str, agent_initial_view:dict)->GameState:

async def reset_agent(self, agent_id:tuple, agent_role:str, agent_initial_view:dict, agent_win_condition_view:dict)->tuple[GameState, GameState]:
raise NotImplementedError

async def _remove_agent_from_game(self, agent_addr):
Expand All @@ -788,6 +795,7 @@ async def _remove_agent_from_game(self, agent_addr):
async with self._agents_lock:
if agent_addr in self.agents:
agent_info["state"] = self._agent_states.pop(agent_addr)
agent_info["goal_state"] = self._agent_goal_states.pop(agent_addr)
agent_info["num_steps"] = self._agent_steps.pop(agent_addr)
agent_info["agent_status"] = self._agent_status.pop(agent_addr)
agent_info["false_positives"] = self._agent_false_positives.pop(agent_addr)
Expand Down Expand Up @@ -816,7 +824,7 @@ async def _remove_agent_from_game(self, agent_addr):
async def step(self, agent_id:tuple, agent_state:GameState, action:Action):
raise NotImplementedError

async def reset(self):
async def reset(self)->bool:
return NotImplemented

def _initialize(self):
Expand Down Expand Up @@ -846,16 +854,17 @@ def goal_dict_satistfied(goal_dict:dict, known_dict: dict)-> bool:
return False
return False
self.logger.debug(f"Checking goal for agent {agent_addr}.")
goal_conditions = self._win_conditions_per_role[self.agents[agent_addr][1]]
state = self._agent_states[agent_addr]
# For each part of the state of the game, check if the conditions are met
target_goal_state = self._agent_goal_states[agent_addr]
self.logger.debug(f"\tGoal conditions: {target_goal_state}.")
goal_reached = {}
goal_reached["networks"] = set(goal_conditions["known_networks"]) <= set(state.known_networks)
goal_reached["known_hosts"] = set(goal_conditions["known_hosts"]) <= set(state.known_hosts)
goal_reached["controlled_hosts"] = set(goal_conditions["controlled_hosts"]) <= set(state.controlled_hosts)
goal_reached["services"] = goal_dict_satistfied(goal_conditions["known_services"], state.known_services)
goal_reached["data"] = goal_dict_satistfied(goal_conditions["known_data"], state.known_data)
goal_reached["known_blocks"] = goal_dict_satistfied(goal_conditions["known_blocks"], state.known_blocks)
goal_reached["networks"] = target_goal_state.known_networks <= state.known_networks
goal_reached["known_hosts"] = target_goal_state.known_hosts <= state.known_hosts
goal_reached["controlled_hosts"] = target_goal_state.controlled_hosts <= state.controlled_hosts
goal_reached["services"] = goal_dict_satistfied(target_goal_state.known_services, state.known_services)
goal_reached["data"] = goal_dict_satistfied(target_goal_state.known_data, state.known_data)
goal_reached["known_blocks"] = goal_dict_satistfied(target_goal_state.known_blocks, state.known_blocks)
self.logger.debug(f"\t{goal_reached}")
return all(goal_reached.values())

Expand Down
4 changes: 3 additions & 1 deletion AIDojoCoordinator/game_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,8 +551,8 @@ def as_graph(self)->tuple:
graph_nodes = {}
node_features = []
controlled = []
edges = []
try:
edges = []
#add known nets
for net in self.known_networks:
graph_nodes[net] = len(graph_nodes)
Expand Down Expand Up @@ -738,6 +738,8 @@ def from_string(cls, string:str)->"GameStatus":
return GameStatus.FORBIDDEN
case "GameStatus.RESET_DONE":
return GameStatus.RESET_DONE
case _:
raise ValueError(f"Invalid GameStatus string: {string}")
def __repr__(self) -> str:
"""
Return the string representation of the GameStatus.
Expand Down
19 changes: 10 additions & 9 deletions AIDojoCoordinator/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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):
"""
Expand Down Expand Up @@ -111,7 +112,7 @@ def observation_as_dict(observation:Observation)->dict:
}
return observation_dict

def parse_log_content(log_content:str)->list:
def parse_log_content(log_content:str)->Optional[list]:
try:
logs = []
data = json.loads(log_content)
Expand Down Expand Up @@ -154,7 +155,7 @@ def read_config_file(self, conf_file_name:str):
self.logger.error(f'Error loading the configuration file{e}')
pass

def read_env_action_data(self, action_name: str) -> dict:
def read_env_action_data(self, action_name: str) -> float:
"""
Generic function to read the known data for any agent and goal of position
"""
Expand Down Expand Up @@ -238,7 +239,7 @@ def read_agents_known_services(self, type_agent: str, type_data: str) -> dict:
known_services = {}
return known_services

def read_agents_known_networks(self, type_agent: str, type_data: str) -> dict:
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
"""
Expand All @@ -251,10 +252,10 @@ def read_agents_known_networks(self, type_agent: str, type_data: str) -> dict:
host_part, net_part = net.split('/')
known_networks.add(Network(host_part, int(net_part)))
except (ValueError, TypeError, netaddr.AddrFormatError):
self.logger('Configuration problem with the known networks')
self.logger.error('Configuration problem with the known networks')
return known_networks

def read_agents_known_hosts(self, type_agent: str, type_data: str) -> dict:
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
"""
Expand All @@ -274,7 +275,7 @@ def read_agents_known_hosts(self, type_agent: str, type_data: str) -> dict:
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) -> dict:
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
"""
Expand Down Expand Up @@ -395,7 +396,7 @@ def get_win_conditions(self, agent_role):
case _:
raise ValueError(f"Unsupported agent role: {agent_role}")

def get_max_steps(self, role=str)->int:
def get_max_steps(self, role=str)->Optional[int]:
"""
Get the max steps based on agent's role
"""
Expand All @@ -409,7 +410,7 @@ def get_max_steps(self, role=str)->int:
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)->dict:
def get_goal_description(self, agent_role)->str:
"""
Get goal description per role
"""
Expand Down Expand Up @@ -554,7 +555,7 @@ def get_starting_position_from_cyst_config(cyst_objects):
if isinstance(obj, NodeConfig):
for active_service in obj.active_services:
if active_service.type == "netsecenv_agent":
print(f"startig processing {obj.id}.{active_service.name}")
print(f"starting processing {obj.id}.{active_service.name}")
hosts = set()
networks = set()
for interface in obj.interfaces:
Expand Down
Loading