@@ -1435,6 +1435,109 @@ def is_delegated_path(self, target_filepath: str) -> bool:
14351435 return False
14361436
14371437
1438+ class SuccinctRoles (Role ):
1439+ """Succinctly defines a hash bin delegation graph.
1440+
1441+ A ``SuccinctRoles`` object describes a delegation graph that covers all
1442+ targets, distributing them uniformly over the delegated roles (i.e. bins)
1443+ in the graph.
1444+
1445+ The total number of bins is 2 to the power of the passed ``bit_length``.
1446+ Targets are assigned to bins by casting the left-most ``bit_length`` of
1447+ bits of the file path hash digest to int, using it as bin index between 0
1448+ and ``2**bit_length - 1``.
1449+
1450+ Bin names are the concatenation of the passed ``name_prefix`` and a hex
1451+ representation of the bin index between separated by a hyphen.
1452+
1453+ The passed ``keyids`` and ``threshold`` is used for each bin, and each bin
1454+ is 'terminating'.
1455+
1456+ For details: https://github.com/theupdateframework/taps/blob/master/tap15.md
1457+
1458+ Args:
1459+ keyids: Signing key identifiers for any bin metadata.
1460+ threshold: Number of keys required to sign any bin metadata.
1461+ bit_length: Number of bits between 1 and 32.
1462+ name_prefix: Prefix of all bin names.
1463+ unrecognized_fields: Dictionary of all attributes that are not managed
1464+ by TUF Metadata API.
1465+
1466+ Raises:
1467+ ValueError, TypeError, AttributeError: Invalid arguments.
1468+ """
1469+
1470+ def __init__ (
1471+ self ,
1472+ keyids : List [str ],
1473+ threshold : int ,
1474+ bit_length : int ,
1475+ name_prefix : str ,
1476+ unrecognized_fields : Optional [Dict [str , Any ]] = None ,
1477+ ) -> None :
1478+ super ().__init__ (keyids , threshold , unrecognized_fields )
1479+
1480+ if bit_length <= 0 or bit_length > 32 :
1481+ raise ValueError ("bit_length must be between 1 and 32" )
1482+ if not isinstance (name_prefix , str ):
1483+ raise ValueError ("name_prefix must be a string" )
1484+
1485+ self .bit_length = bit_length
1486+ self .name_prefix = name_prefix
1487+
1488+ def __eq__ (self , other : Any ) -> bool :
1489+ if not isinstance (other , SuccinctRoles ):
1490+ return False
1491+
1492+ return (
1493+ super ().__eq__ (other )
1494+ and self .bit_length == other .bit_length
1495+ and self .name_prefix == other .name_prefix
1496+ )
1497+
1498+ @classmethod
1499+ def from_dict (cls , role_dict : Dict [str , Any ]) -> "SuccinctRoles" :
1500+ """Creates ``SuccinctRoles`` object from its json/dict representation.
1501+
1502+ Raises:
1503+ ValueError, KeyError, AttributeError, TypeError: Invalid arguments.
1504+ """
1505+ keyids = role_dict .pop ("keyids" )
1506+ threshold = role_dict .pop ("threshold" )
1507+ bit_length = role_dict .pop ("bit_length" )
1508+ name_prefix = role_dict .pop ("name_prefix" )
1509+ # All fields left in the role_dict are unrecognized.
1510+ return cls (keyids , threshold , bit_length , name_prefix , role_dict )
1511+
1512+ def to_dict (self ) -> Dict [str , Any ]:
1513+ """Returns the dict representation of self."""
1514+ base_role_dict = super ().to_dict ()
1515+ return {
1516+ "bit_length" : self .bit_length ,
1517+ "name_prefix" : self .name_prefix ,
1518+ ** base_role_dict ,
1519+ }
1520+
1521+ def get_role_for_target (self , target_filepath : str ) -> str :
1522+ """Calculates the name of the delegated role responsible for
1523+ ``target_filepath``.
1524+
1525+ Args:
1526+ target_filepath: URL path to a target file, relative to a base
1527+ targets URL.
1528+ """
1529+ hasher = sslib_hash .digest (algorithm = "sha256" )
1530+ hasher .update (target_filepath .encode ("utf-8" ))
1531+
1532+ # We can't ever need more than 4 bytes (32 bits).
1533+ hash_bytes = hasher .digest ()[:4 ]
1534+ # Right shift hash bytes, so that we only have the leftmost
1535+ # bit_length bits that we care about.
1536+ shift_value = 32 - self .bit_length
1537+ bin_number = int .from_bytes (hash_bytes , byteorder = "big" ) >> shift_value
1538+ return f"{ self .name_prefix } -{ bin_number } "
1539+
1540+
14381541class Delegations :
14391542 """A container object storing information about all delegations.
14401543
0 commit comments