blob: 299e36bf7070088846fa111f9dafbe45bbaadcfd [file] [log] [blame]
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Common classes for HWID v3 operation."""
import collections
import copy
import os
import pprint
import re
import factory_common # pylint: disable=W0611
from cros.factory import common, schema, rule
from cros.factory.test import utils
# The expected location of HWID data within a factory image or the
# chroot.
DEFAULT_HWID_DATA_PATH = (
os.path.join(os.environ['CROS_WORKON_SRCROOT'],
'src', 'platform', 'chromeos-hwid', 'v3')
if utils.in_chroot()
else '/usr/local/factory/hwid')
def ProbeBoard():
"""Probes the board name by looking up the CHROMEOS_RELEASE_BOARD variable
in /etc/lsb-release.
Returns:
The probed board name as a string.
Raises:
HWIDException when probe error.
"""
LSB_RELEASE_FILE = '/etc/lsb-release'
LSB_BOARD_RE = re.compile(r'^CHROMEOS_RELEASE_BOARD=(\w+)$', re.M)
if utils.in_chroot():
raise HWIDException('Unable to determine board in chroot')
if not os.path.exists(LSB_RELEASE_FILE):
raise HWIDException('%r does not exist, unable to determine board' %
LSB_RELEASE_FILE)
try:
with open(LSB_RELEASE_FILE) as f:
board = LSB_BOARD_RE.findall(f.read())[0].rpartition('_')[-1]
except IndexError:
raise HWIDException('Cannot determine board from %r' % LSB_RELEASE_FILE)
return board
# A named tuple to store the probed component name and the error if any.
ProbedComponentResult = collections.namedtuple(
'ProbedComponentResult', ['component_name', 'probed_values', 'error'])
UNPROBEABLE_COMPONENT_ERROR = lambda comp_cls: (
'Component class %r is unprobeable' % comp_cls)
MISSING_COMPONENT_ERROR = lambda comp_cls: 'Missing %r component' % comp_cls
AMBIGUOUS_COMPONENT_ERROR = lambda comp_cls, probed_value, comp_names: (
'Ambiguous probe values %s of %r component. Possible components are: %r' %
(pprint.pformat(probed_value, indent=2), comp_cls, sorted(comp_names)))
UNSUPPORTED_COMPONENT_ERROR = lambda comp_cls, probed_value: (
'Unsupported %r component found with probe result %s '
'(no matching name in the component DB)' % (
comp_cls, pprint.pformat(probed_value, indent=2)))
class HWIDException(Exception):
pass
class HWID(object):
"""A class that holds all the context of a HWID.
It verifies the correctness of the HWID when a new HWID object is created.
This class is mainly for internal use. User should not create a HWID object
directly with the constructor.
With bom (obatined from hardware prober) and board-specific component
database, HWID encoder can derive binary_string and encoded_string.
Reversely, with encoded_string and board-specific component database, HWID
decoder can derive binary_string and bom.
Attributes:
database: A board-specific Database object.
binary_string: A binary string. Ex: "0000010010..." It is used for fast
component lookups as each component is represented by one or multiple
bits at fixed positions.
encoded_string: An encoded string with board name and checksum. For example:
"CHROMEBOOK ASDF-2345", where CHROMEBOOK is the board name and 45 is the
checksum. Compare to binary_string, it is human-trackable.
bom: A BOM object.
skip_check: Skips HWID self verification. This is needed when we want to
create a HWID object skeleton for further processing, e.g. a skeleton
HWID object to pass to rule evaluation to generate the final HWID.
Defaults to False.
Raises:
HWIDException if an invalid arg is found.
"""
HEADER_BITS = 5
COMPONENT_STATUS = utils.Enum(['supported', 'deprecated', 'unsupported'])
def __init__(self, database, binary_string, encoded_string, bom,
skip_check=False):
self.database = database
self.binary_string = binary_string
self.encoded_string = encoded_string
self.bom = bom
if not skip_check:
self.VerifySelf()
def VerifySelf(self):
"""Verifies the HWID object itself.
Raises:
HWIDException on verification error.
"""
# pylint: disable=W0404
from cros.factory.hwid.decoder import BinaryStringToBOM
from cros.factory.hwid.decoder import EncodedStringToBinaryString
self.database.VerifyBOM(self.bom)
self.database.VerifyBinaryString(self.binary_string)
self.database.VerifyEncodedString(self.encoded_string)
if (EncodedStringToBinaryString(self.database, self.encoded_string) !=
self.binary_string):
raise HWIDException(
'Encoded string %s does not decode to binary string %r' %
(self.encoded_string, self.binary_string))
if BinaryStringToBOM(self.database, self.binary_string) != self.bom:
def GetComponentsDifferences(decoded, target):
results = []
for comp_cls in set(
decoded.components.keys() + target.components.keys()):
if comp_cls not in decoded.components:
results.append('Decoded: does not exist. BOM: %r' %
target.components[comp_cls])
elif comp_cls not in target.components:
results.append('Decoded: %r. BOM: does not exist.' %
decoded.components[comp_cls])
elif decoded.components[comp_cls] != target.components[comp_cls]:
results.append('Decoded: %r != BOM: %r' %
(decoded.components[comp_cls], target.components[comp_cls]))
return results
raise HWIDException(
'Binary string %r does not decode to BOM. Differences: %r' %
(self.binary_string, GetComponentsDifferences(
self.bom, BinaryStringToBOM(self.database, self.binary_string))))
# No exception. Everything is good!
def VerifyComponentStatus(self, rma_mode=False):
"""Verifies the status of all components.
Accepts all 'supported' components, rejects all 'unsupported' components,
and accepts/rejects 'deprecated' components if rma_mode is True/False.
Args:
rma_mode: Whether to verify components status in RMA mode.
Raises:
HWIDException is verification fails.
"""
for comp_cls, comps in self.bom.components.iteritems():
for comp in comps:
comp_name = comp.component_name
if not comp_name:
continue
status = self.database.components.GetComponentStatus(
comp_cls, comp_name)
if status == HWID.COMPONENT_STATUS.supported:
continue
elif status == HWID.COMPONENT_STATUS.unsupported:
raise HWIDException('Found unsupported component of %r: %r' %
(comp_cls, comp_name))
elif status == HWID.COMPONENT_STATUS.deprecated:
if not rma_mode:
raise HWIDException(
'Not in RMA mode. Found deprecated component of %r: %r' %
(comp_cls, comp_name))
def VerifyProbeResult(self, probe_result):
"""Verifies that the probe result matches the settings encoded in the HWID
object.
Args:
probe_result: A YAML string of the probe result, which is usually the
output of the probe command.
Raises:
HWIDException on verification error.
"""
self.database.VerifyComponents(probe_result)
probed_bom = self.database.ProbeResultToBOM(probe_result)
def PackProbedValues(bom, comp_cls):
results = []
for e in bom.components[comp_cls]:
if e.probed_values is None:
continue
matched_component = self.database.components.MatchComponentsFromValues(
comp_cls, e.probed_values)
if matched_component:
results.extend(matched_component.keys())
return results
for comp_cls in self.database.components.GetRequiredComponents():
if comp_cls not in self.database.components.probeable:
continue
probed_components = common.MakeSet(PackProbedValues(probed_bom, comp_cls))
expected_components = common.MakeSet(PackProbedValues(self.bom, comp_cls))
extra_components = probed_components - expected_components
missing_components = expected_components - probed_components
if extra_components or missing_components:
err_msg = 'Component class %r' % comp_cls
if extra_components:
err_msg += ' has extra components: %r' % sorted(extra_components)
if missing_components:
if extra_components:
err_msg += ' and'
err_msg += ' is missing components: %r' % sorted(missing_components)
err_msg += '. Expected components are: %r' % (
sorted(expected_components) if expected_components else None)
raise HWIDException(err_msg)
def GetLabels(self):
"""Gets from the database the labels of all the components encoded in this
HWID object.
Returns:
A dict of the form:
{
'component_class_1': {
'component_name_1': {
'label_key_1': 'LABEL_1_VALUE',
'label_key_2': 'LABEL_2_VALUE',
...
},
...
},
'component_class_2': {
'component_name_2': None # No labels were defined on this component.
},
...
}
"""
results = collections.defaultdict(dict)
for comp_cls, comp_data in self.bom.components.iteritems():
for comp_value in comp_data:
if comp_value.component_name:
db_comp_attrs = self.database.components.GetComponentAttributes(
comp_cls, comp_value.component_name)
results[comp_cls][comp_value.component_name] = copy.deepcopy(
db_comp_attrs.get('labels', None))
return results
class BOM(object):
"""A class that holds all the information regarding a BOM.
Attributes:
board: A string of board name.
encoding_pattern_index: An int indicating the encoding pattern. Currently,
only 0 is used.
image_id: An int indicating the image id.
components: A dict that maps component classes to a list of
ProbedComponentResult.
encoded_fields: A dict that maps each encoded field to its index.
Raises:
SchemaException if invalid argument format is found.
"""
_COMPONENTS_SCHEMA = schema.Dict(
'bom',
key_type=schema.Scalar('component class', str),
value_type=schema.List(
'list of ProbedComponentResult',
schema.Tuple('ProbedComponentResult',
[schema.Optional(schema.Scalar('component name', str)),
schema.Optional(schema.Dict('probed_values',
key_type=schema.Scalar('key', str),
value_type=schema.AnyOf([
schema.Scalar('value', str),
schema.Scalar('value', rule.Value)]))),
schema.Optional(schema.Scalar('error', str))])))
def __init__(self, board, encoding_pattern_index, image_id,
components, encoded_fields):
self.board = board
self.encoding_pattern_index = encoding_pattern_index
self.image_id = image_id
self.components = components
self.encoded_fields = encoded_fields
BOM._COMPONENTS_SCHEMA.Validate(self.components)
def Duplicate(self):
"""Duplicates this BOM object.
Returns:
A deepcopy of the original BOM object.
"""
return copy.deepcopy(self)
def __eq__(self, op2):
if not isinstance(op2, BOM):
return False
return self.__dict__ == op2.__dict__
def __ne__(self, op2):
return not self.__eq__(op2)