def _export_register(self, register_name: str, compute_inverses: bool) -> bytes: """Generate binary output for single register.""" register = BitArray(length=32) user_config = self.user_config.get(register_name, dict()) inverse_present = False for field in self._get_bitfields(register_name, exclude_computed=False): name = field.attrib["name"] offset = parse_int(field.attrib["offset"]) width = parse_int(field.attrib["width"]) # chcek whether there's a need to calculate inverse values inverse_present |= name == "INVERSE_VALUE" # The configuration allows to configure the whole register with single value if isinstance(user_config, str): temp_value = user_config else: temp_value = user_config.get(name) or field.attrib["reset_value"] value = parse_int(temp_value) # due to endianess we fill the bits from the end, therefore there's '-' in position # pos = 0 means offset = 0, means end of the BitArray register.overwrite(bs=BitArray(f"uint:{width}={value}"), pos=-(offset + width)) if compute_inverses and inverse_present: # NOTE: For now we'll assume the INVERSES are 16b long and inverts bits[15:0] # should this change in the future a data model change is necessary # NOTE: invert method changes bits in-place, thus we need to call it on separate object # calling invert() after slicing doesn't work for BitArray b_lower = register[16:] b_lower.invert() register.overwrite(b_lower, 0) # swapping bytes from big endian into little register.byteswap() return register.bytes
def _parse_register(self, register_name: str, data: bytes) -> Union[str, dict]: """Parse individual register, returns wither one 32b value or dict of bitfields.""" register = {} bits = BitArray(data) # data is stored in little endian, but processed in big endian bits.byteswap() for field in self._get_bitfields(register_name, exclude_computed=False): width = parse_int(field.attrib["width"]) # exit early if we found a single 32b field if width == 32: return format_value(bits.uint, width) name = field.attrib["name"] offset = parse_int(field.attrib["offset"]) # OK, what the hell is that slicing about?! # offset is marked from the end of the bitarray not the begging like in a list # e.g.: ba = BitArray('0b00001100'), we want to extract bitfields of width=2 and offset=2 ('11') # again offset=2 means 2 bits from the end # BitArray supports negative indexing like an regular python list does: last bit has index -1 # that means we want to extract bits with indecies -4,-3 => [-4:-2] # HOWEVER: if we would want to extract 2 bits in the end (offset=0) # we would need to use [-offset:] slice or [-offset:None] slice_end = None if offset == 0 else -offset filed_bits = bits[-(offset + width): slice_end] register[name] = format_value(filed_bits.uint, width) return register
def export(self, add_seal: bool = False, compute_inverses: bool = False) -> bytes: """Generate binary output.""" data = bytearray(self.BINARY_SIZE) for reg in self._get_registers(exclude_computed=False): name = reg.attrib["name"] width = parse_int(reg.attrib["width"]) offset = parse_int(reg.attrib["offset"]) assert width == 32, "Don't know how to handle non-32b registers" register = self._export_register(name, compute_inverses) # rewriting 4B at the time data[offset: offset + 4] = register # ROTKH may or may not be present, derived class defines its presense if self.HAS_ROTKH: rotkh_data = self.rotkh or self._calc_rotkh() rothk_start = self._get_rotkh_start_address() data[rothk_start: rothk_start + self.ROTKH_SIZE] = rotkh_data if add_seal: seal_start = self._get_seal_start_address() seal_cout = self._get_seal_count() data[seal_start: seal_start + seal_cout * 4] = self.MARK * seal_cout assert len(data) == self.BINARY_SIZE, f'The size of data is {len(data)}, is not equal to {self.BINARY_SIZE}' return bytes(data)
def export(self, add_hash: bool = False, compute_inverses: bool = False) -> bytes: """Generate binary output.""" data = bytearray(self.BINARY_SIZE) for reg in self._get_registers(exclude_computed=False): name = reg.attrib["name"] width = parse_int(reg.attrib["width"]) offset = parse_int(reg.attrib["offset"]) assert width == 32, "Don't know how to handle non-32b registers" register = self._export_register(name, compute_inverses) # rewriting 4B at the time data[offset:offset + 4] = register # ROTKH may or may not be present, derived class defines its presense if self.HAS_ROTKH: rotkh_data = self.rotkh or self._calc_rotkh() rothk_start = self._get_rotkh_start_address() data[rothk_start:rothk_start + self.ROTKH_SIZE] = rotkh_data if add_hash: sha_start = self._get_sha_start_address() data[sha_start:sha_start + self.SHA_SIZE] = openssl_backend.hash( data[:sha_start]) return bytes(data)
def parse(self, data: bytes, exclude_computed: bool = True) -> dict: """Return a user config JSON object based on input data.""" user_config = {} for reg in self._get_registers(exclude_computed=exclude_computed): name = reg.attrib["name"] width = parse_int(reg.attrib["width"]) offset = parse_int(reg.attrib["offset"]) assert width == 32, "Don't know how to handle non-32b registers" reg_config = self._parse_register(name, data[offset: offset + 4]) user_config[name] = reg_config return user_config
def _get_sha_start_address(self) -> int: """Return the offset of the first SHA_DIGEST register.""" return parse_int(self._get_register(self.SHA_START_REGISTER).attrib["offset"])
def _get_rotkh_start_address(self) -> int: """Return the offset of the first ROTKHx register defined as ROTKH_START_REGISTER.""" return parse_int(self._get_register(self.ROTKH_START_REGISTER).attrib["offset"])
def test_parse_int_invalid_input(test_input): """ Test invalid inputs for parse_int() """ with pytest.raises(ValueError): parse_int(test_input)
def test_parse_int(test_input, expected): assert parse_int(test_input) == expected
def _get_seal_start_address(self) -> int: start = self.config['devices'][self.device].get('seal_start') if start is None: start = self.config['seal_start'] assert start, "Can't find 'seal_start' in database.json" return parse_int(self._get_register(start).attrib['offset'])