blob: eab3f91007544911ad6801e7d5a5ce47cafbf172 [file] [log] [blame]
Ricky Liangf78dfcf2013-06-03 11:18:13 +08001# -*- coding: utf-8 -*-
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Common classes for HWID v3 operation."""
8
9import collections
10import copy
11import os
12import pprint
13import re
14
15import factory_common # pylint: disable=W0611
16from cros.factory import common, schema, rule
17from cros.factory.test import utils
18
19# The expected location of HWID data within a factory image or the
20# chroot.
21DEFAULT_HWID_DATA_PATH = (
22 os.path.join(os.environ['CROS_WORKON_SRCROOT'],
23 'src', 'platform', 'chromeos-hwid', 'v3')
24 if utils.in_chroot()
25 else '/usr/local/factory/hwid')
26
27
28def ProbeBoard():
29 """Probes the board name by looking up the CHROMEOS_RELEASE_BOARD variable
30 in /etc/lsb-release.
31
32 Returns:
33 The probed board name as a string.
34
35 Raises:
36 HWIDException when probe error.
37 """
38 LSB_RELEASE_FILE = '/etc/lsb-release'
39 LSB_BOARD_RE = re.compile(r'^CHROMEOS_RELEASE_BOARD=(\w+)$', re.M)
40 if utils.in_chroot():
41 raise HWIDException('Unable to determine board in chroot')
42 if not os.path.exists(LSB_RELEASE_FILE):
43 raise HWIDException('%r does not exist, unable to determine board' %
44 LSB_RELEASE_FILE)
45 try:
46 with open(LSB_RELEASE_FILE) as f:
47 board = LSB_BOARD_RE.findall(f.read())[0].rpartition('_')[-1]
48 except IndexError:
49 raise HWIDException('Cannot determine board from %r' % LSB_RELEASE_FILE)
50 return board
51
52
53# A named tuple to store the probed component name and the error if any.
54ProbedComponentResult = collections.namedtuple(
55 'ProbedComponentResult', ['component_name', 'probed_values', 'error'])
56
57UNPROBEABLE_COMPONENT_ERROR = lambda comp_cls: (
58 'Component class %r is unprobeable' % comp_cls)
59MISSING_COMPONENT_ERROR = lambda comp_cls: 'Missing %r component' % comp_cls
60AMBIGUOUS_COMPONENT_ERROR = lambda comp_cls, probed_value, comp_names: (
61 'Ambiguous probe values %s of %r component. Possible components are: %r' %
62 (pprint.pformat(probed_value, indent=2), comp_cls, sorted(comp_names)))
63UNSUPPORTED_COMPONENT_ERROR = lambda comp_cls, probed_value: (
64 'Unsupported %r component found with probe result %s '
65 '(no matching name in the component DB)' % (
66 comp_cls, pprint.pformat(probed_value, indent=2)))
67
68
69class HWIDException(Exception):
70 pass
71
72
73class HWID(object):
74 """A class that holds all the context of a HWID.
75
76 It verifies the correctness of the HWID when a new HWID object is created.
77 This class is mainly for internal use. User should not create a HWID object
78 directly with the constructor.
79
80 With bom (obatined from hardware prober) and board-specific component
81 database, HWID encoder can derive binary_string and encoded_string.
82 Reversely, with encoded_string and board-specific component database, HWID
83 decoder can derive binary_string and bom.
84
85 Attributes:
86 database: A board-specific Database object.
87 binary_string: A binary string. Ex: "0000010010..." It is used for fast
88 component lookups as each component is represented by one or multiple
89 bits at fixed positions.
90 encoded_string: An encoded string with board name and checksum. For example:
91 "CHROMEBOOK ASDF-2345", where CHROMEBOOK is the board name and 45 is the
92 checksum. Compare to binary_string, it is human-trackable.
93 bom: A BOM object.
94 skip_check: Skips HWID self verification. This is needed when we want to
95 create a HWID object skeleton for further processing, e.g. a skeleton
96 HWID object to pass to rule evaluation to generate the final HWID.
97 Defaults to False.
98
99 Raises:
100 HWIDException if an invalid arg is found.
101 """
102 HEADER_BITS = 5
103 COMPONENT_STATUS = utils.Enum(['supported', 'deprecated', 'unsupported'])
104
105 def __init__(self, database, binary_string, encoded_string, bom,
106 skip_check=False):
107 self.database = database
108 self.binary_string = binary_string
109 self.encoded_string = encoded_string
110 self.bom = bom
111 if not skip_check:
112 self.VerifySelf()
113
114 def VerifySelf(self):
115 """Verifies the HWID object itself.
116
117 Raises:
118 HWIDException on verification error.
119 """
120 # pylint: disable=W0404
121 from cros.factory.hwid.decoder import BinaryStringToBOM
122 from cros.factory.hwid.decoder import EncodedStringToBinaryString
123 self.database.VerifyBOM(self.bom)
124 self.database.VerifyBinaryString(self.binary_string)
125 self.database.VerifyEncodedString(self.encoded_string)
126 if (EncodedStringToBinaryString(self.database, self.encoded_string) !=
127 self.binary_string):
128 raise HWIDException(
129 'Encoded string %s does not decode to binary string %r' %
130 (self.encoded_string, self.binary_string))
131 if BinaryStringToBOM(self.database, self.binary_string) != self.bom:
132 def GetComponentsDifferences(decoded, target):
133 results = []
134 for comp_cls in set(
135 decoded.components.keys() + target.components.keys()):
136 if comp_cls not in decoded.components:
137 results.append('Decoded: does not exist. BOM: %r' %
138 target.components[comp_cls])
139 elif comp_cls not in target.components:
140 results.append('Decoded: %r. BOM: does not exist.' %
141 decoded.components[comp_cls])
142 elif decoded.components[comp_cls] != target.components[comp_cls]:
143 results.append('Decoded: %r != BOM: %r' %
144 (decoded.components[comp_cls], target.components[comp_cls]))
145 return results
146 raise HWIDException(
147 'Binary string %r does not decode to BOM. Differences: %r' %
148 (self.binary_string, GetComponentsDifferences(
149 self.bom, BinaryStringToBOM(self.database, self.binary_string))))
150 # No exception. Everything is good!
151
152 def VerifyComponentStatus(self, rma_mode=False):
153 """Verifies the status of all components.
154
155 Accepts all 'supported' components, rejects all 'unsupported' components,
156 and accepts/rejects 'deprecated' components if rma_mode is True/False.
157
158 Args:
159 rma_mode: Whether to verify components status in RMA mode.
160
161 Raises:
162 HWIDException is verification fails.
163 """
164 for comp_cls, comps in self.bom.components.iteritems():
165 for comp in comps:
166 comp_name = comp.component_name
167 if not comp_name:
168 continue
169
170 status = self.database.components.GetComponentStatus(
171 comp_cls, comp_name)
172 if status == HWID.COMPONENT_STATUS.supported:
173 continue
174 elif status == HWID.COMPONENT_STATUS.unsupported:
175 raise HWIDException('Found unsupported component of %r: %r' %
176 (comp_cls, comp_name))
177 elif status == HWID.COMPONENT_STATUS.deprecated:
178 if not rma_mode:
179 raise HWIDException(
180 'Not in RMA mode. Found deprecated component of %r: %r' %
181 (comp_cls, comp_name))
182
183 def VerifyProbeResult(self, probe_result):
184 """Verifies that the probe result matches the settings encoded in the HWID
185 object.
186
187 Args:
188 probe_result: A YAML string of the probe result, which is usually the
189 output of the probe command.
190
191 Raises:
192 HWIDException on verification error.
193 """
194 self.database.VerifyComponents(probe_result)
195 probed_bom = self.database.ProbeResultToBOM(probe_result)
196 def PackProbedValues(bom, comp_cls):
197 results = []
198 for e in bom.components[comp_cls]:
199 if e.probed_values is None:
200 continue
201 matched_component = self.database.components.MatchComponentsFromValues(
202 comp_cls, e.probed_values)
203 if matched_component:
204 results.extend(matched_component.keys())
205 return results
206 for comp_cls in self.database.components.GetRequiredComponents():
207 if comp_cls not in self.database.components.probeable:
208 continue
209 probed_components = common.MakeSet(PackProbedValues(probed_bom, comp_cls))
210 expected_components = common.MakeSet(PackProbedValues(self.bom, comp_cls))
211 extra_components = probed_components - expected_components
212 missing_components = expected_components - probed_components
213 if extra_components or missing_components:
Ricky Liangb8b1bf22013-06-07 17:11:38 +0800214 err_msg = 'Component class %r' % comp_cls
215 if extra_components:
216 err_msg += ' has extra components: %r' % sorted(extra_components)
217 if missing_components:
218 if extra_components:
219 err_msg += ' and'
220 err_msg += ' is missing components: %r' % sorted(missing_components)
221 err_msg += '. Expected components are: %r' % (
222 sorted(expected_components) if expected_components else None)
223 raise HWIDException(err_msg)
Ricky Liangf78dfcf2013-06-03 11:18:13 +0800224
225 def GetLabels(self):
226 """Gets from the database the labels of all the components encoded in this
227 HWID object.
228
229 Returns:
230 A dict of the form:
231 {
232 'component_class_1': {
233 'component_name_1': {
234 'label_key_1': 'LABEL_1_VALUE',
235 'label_key_2': 'LABEL_2_VALUE',
236 ...
237 },
238 ...
239 },
240 'component_class_2': {
241 'component_name_2': None # No labels were defined on this component.
242 },
243 ...
244 }
245 """
246 results = collections.defaultdict(dict)
247 for comp_cls, comp_data in self.bom.components.iteritems():
248 for comp_value in comp_data:
249 if comp_value.component_name:
250 db_comp_attrs = self.database.components.GetComponentAttributes(
251 comp_cls, comp_value.component_name)
252 results[comp_cls][comp_value.component_name] = copy.deepcopy(
253 db_comp_attrs.get('labels', None))
254 return results
255
256
257class BOM(object):
258 """A class that holds all the information regarding a BOM.
259
260 Attributes:
261 board: A string of board name.
262 encoding_pattern_index: An int indicating the encoding pattern. Currently,
263 only 0 is used.
264 image_id: An int indicating the image id.
265 components: A dict that maps component classes to a list of
266 ProbedComponentResult.
267 encoded_fields: A dict that maps each encoded field to its index.
268
269 Raises:
270 SchemaException if invalid argument format is found.
271 """
272 _COMPONENTS_SCHEMA = schema.Dict(
273 'bom',
274 key_type=schema.Scalar('component class', str),
275 value_type=schema.List(
276 'list of ProbedComponentResult',
277 schema.Tuple('ProbedComponentResult',
278 [schema.Optional(schema.Scalar('component name', str)),
279 schema.Optional(schema.Dict('probed_values',
280 key_type=schema.Scalar('key', str),
281 value_type=schema.AnyOf([
282 schema.Scalar('value', str),
283 schema.Scalar('value', rule.Value)]))),
284 schema.Optional(schema.Scalar('error', str))])))
285
286 def __init__(self, board, encoding_pattern_index, image_id,
287 components, encoded_fields):
288 self.board = board
289 self.encoding_pattern_index = encoding_pattern_index
290 self.image_id = image_id
291 self.components = components
292 self.encoded_fields = encoded_fields
293 BOM._COMPONENTS_SCHEMA.Validate(self.components)
294
295 def Duplicate(self):
296 """Duplicates this BOM object.
297
298 Returns:
299 A deepcopy of the original BOM object.
300 """
301 return copy.deepcopy(self)
302
303 def __eq__(self, op2):
304 return self.__dict__ == op2.__dict__
305
306 def __ne__(self, op2):
307 return not self.__eq__(op2)