Skip to content

Commit 92270e7

Browse files
committed
Implement _preorder_depth_first_walk recursively
Use a recursive call of _preorder_depth_first_walk instead of a stack to implement the same algorithm. Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
1 parent d7daf08 commit 92270e7

1 file changed

Lines changed: 55 additions & 66 deletions

File tree

tuf/ngclient/updater.py

Lines changed: 55 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ def get_one_valid_targetinfo(
171171
RepositoryError: Metadata failed to verify in some way
172172
TODO: download-related errors
173173
"""
174-
return self._preorder_depth_first_walk(target_path)
174+
targetinfo, dummy = self._preorder_depth_first_walk(
175+
target_path, set(), ("targets", "root"), self.config.max_delegations
176+
)
177+
return targetinfo
175178

176179
@staticmethod
177180
def updated_targets(
@@ -370,77 +373,63 @@ def _load_targets(self, role: str, parent_role: str) -> None:
370373
self._persist_metadata(role, data)
371374

372375
def _preorder_depth_first_walk(
373-
self, target_filepath: str
374-
) -> Union[Dict[str, Any], None]:
376+
self,
377+
target_filepath: str,
378+
visited_role_names: Set[Tuple[str, str]],
379+
current_role_pair: Tuple[str, str],
380+
number_of_delegations: int,
381+
) -> Tuple[Union[Dict[str, Any], None], bool]:
375382
"""
376383
Interrogates the tree of target delegations in order of appearance
377384
(which implicitly order trustworthiness), and returns the matching
378385
target found in the most trusted role.
379386
"""
380-
381-
role_names = [("targets", "root")]
382-
visited_role_names: Set[Tuple[str, str]] = set()
383-
number_of_delegations = self.config.max_delegations
384-
387+
targetinfo = None
388+
terminated = False
385389
# Preorder depth-first traversal of the graph of target delegations.
386-
while number_of_delegations > 0 and len(role_names) > 0:
387-
388-
# Pop the role name from the top of the stack.
389-
role_name, parent_role = role_names.pop(-1)
390-
391-
# Skip any visited current role to prevent cycles.
392-
if (role_name, parent_role) in visited_role_names:
393-
logger.debug("Skipping visited current role %s", role_name)
394-
continue
395-
396-
# The metadata for 'role_name' must be downloaded/updated before
397-
# its targets, delegations, and child roles can be inspected.
398-
self._load_targets(role_name, parent_role)
399-
400-
role_metadata: Targets = self._trusted_set[role_name].signed
401-
target = role_metadata.targets.get(target_filepath)
402-
403-
if target is not None:
404-
logger.debug("Found target in current role %s", role_name)
405-
return {"filepath": target_filepath, "fileinfo": target}
406-
407-
# After preorder check, add current role to set of visited roles.
408-
visited_role_names.add((role_name, parent_role))
409-
410-
# And also decrement number of visited roles.
411-
number_of_delegations -= 1
412-
413-
if role_metadata.delegations is not None:
414-
child_roles_to_visit = []
415-
# NOTE: This may be a slow operation if there are many
416-
# delegated roles.
417-
for child_role in role_metadata.delegations.roles:
418-
if child_role.is_in_trusted_paths(target_filepath):
419-
logger.debug("Adding child role %s", child_role.name)
420-
421-
child_roles_to_visit.append(
422-
(child_role.name, role_name)
423-
)
424-
if child_role.terminating:
425-
logger.debug("Not backtracking to other roles.")
426-
role_names = []
427-
break
428-
# Push 'child_roles_to_visit' in reverse order of appearance
429-
# onto 'role_names'. Roles are popped from the end of
430-
# the 'role_names' list.
431-
child_roles_to_visit.reverse()
432-
role_names.extend(child_roles_to_visit)
433-
434-
if number_of_delegations == 0 and len(role_names) > 0:
435-
logger.debug(
436-
"%d roles left to visit, but allowed to "
437-
"visit at most %d delegations.",
438-
len(role_names),
439-
self.config.max_delegations,
440-
)
441-
442-
# If this point is reached then target is not found, return None
443-
return None
390+
if number_of_delegations <= 0:
391+
return targetinfo, terminated
392+
393+
# Pop the role name from the top of the stack.
394+
role_name, parent_role = current_role_pair
395+
396+
# The metadata for 'role_name' must be downloaded/updated before
397+
# its targets, delegations, and child roles can be inspected.
398+
self._load_targets(role_name, parent_role)
399+
role_metadata: Targets = self._trusted_set[role_name].signed
400+
target = role_metadata.targets.get(target_filepath)
401+
402+
if target is not None:
403+
logger.debug("Found target in current role %s", role_name)
404+
targetinfo = {"filepath": target_filepath, "fileinfo": target}
405+
return targetinfo, terminated
406+
407+
# After preorder check, add current role to set of visited roles.
408+
visited_role_names.add((role_name, parent_role))
409+
410+
# And also decrement number of visited roles.
411+
number_of_delegations -= 1
412+
if role_metadata.delegations is not None:
413+
for child_role in role_metadata.delegations.roles:
414+
# Skip any visited current role to prevent cycles.
415+
if (child_role.name, role_name) in visited_role_names:
416+
continue
417+
418+
if child_role.is_in_trusted_paths(target_filepath):
419+
420+
targetinfo, terminated = self._preorder_depth_first_walk(
421+
target_filepath,
422+
visited_role_names,
423+
(child_role.name, role_name),
424+
number_of_delegations,
425+
)
426+
427+
if child_role.terminating or terminated:
428+
terminated = True
429+
logger.debug("Not backtracking to other roles.")
430+
break
431+
432+
return targetinfo, terminated
444433

445434

446435
def _ensure_trailing_slash(url: str):

0 commit comments

Comments
 (0)