blob: fbfea4b21040c42d0a323925753e72346ad6bd16 [file] [log] [blame]
# Copyright 2018 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.
"""Module for configless fields.
Configless fields are some numeric fields in HWID that can be decoded without
board / project specific database (e.g. HWID database).
"""
from six.moves import xrange
import factory_common # pylint: disable=unused-import
from cros.factory.hwid.v3 import common
from cros.factory.test import device_data_constants
class ConfiglessFields(object):
"""ConfiglessFields class
The format of configless fields is decided by FIELDS:
`hex(<FIELDS[0]>)-hex(<FIELDS[1]>)-...-hex(<FIELDS[-1]>)`
And the content of feature list field is decided by FEATURE_LIST[version]
where version is FIELDS[0]. To make it easier to extend (that is, add
'has_new_feature' to the end of existing FEATURE_LIST[version]), always add
a leading 1, so we can determine the length of feature list when it was
encoded.
For example,
FIELDS = [
'version',
'memory',
'storage',
'feature_list'
]
FEATURE_LIST = {
0: [
'has_touchscreen',
'has_touchpad',
'has_stylus',
'has_front_camera',
'has_rear_camera',
'has_fingerprint',
'is_convertible',
'is_rma_device'
]
}
encoded string "0-8-74-180" represents version 0, 8G memory, 116G storage and
has touchscreen('180' is 0b110000000).
If we extend version 0,
FEATURE_LIST = {
0: [
'has_touchscreen',
'has_touchpad',
'has_stylus',
'has_front_camera',
'has_rear_camera',
'has_fingerprint',
'is_convertible',
'is_rma_device',
'has_new_feature',
]
}
then, feature list field '180' means has touchscreen and don't have value for
'has_new_feature'.
"""
FIELDS = [
'version', # version of feature list
'memory',
'storage',
'feature_list'
]
FEATURE_LIST = {
0: [
'has_touchscreen',
'has_touchpad',
'has_stylus',
'has_front_camera',
'has_rear_camera',
'has_fingerprint',
'is_convertible',
'is_rma_device'
]
}
@classmethod
def Encode(cls, db, bom, device_info, version, is_rma_device):
"""Return a encoded string according to version.
Args:
db: a Database object that is used to provide device-specific
information.
:type database: cros.factory.hwid.v3.database.Database
bom: a BOM object that lists components on current device.
:type bom: cros.factory.hwid.v3.bom.BOM
device_info: a dictionary follows definition in `device_data`.
:type device_info: dict
version: use _FeatureList_{version} to encode/decode feature list field.
:type version: int
Returns:
A string of encoded configless fields.
"""
getter = _ConfiglessFieldGetter(
db, bom, device_info, version, is_rma_device)
return '-'.join(
hex(getter(field)).upper().replace('0X', '') for field in cls.FIELDS)
@classmethod
def Decode(cls, encoded_string):
"""Return a dict of decoded info.
Args:
encoded_string: a string generated by ConfiglessFields.Encode
:type encoded_string: string
Returns:
A decoded dict.
For example, a return dict
{
'version': 0,
'memory': 8,
'storage' 116,
'feature_list': {
'has_touchscreen': 1,
'has_touchpad': 0,
'has_stylus': 0,
'has_front_camera': 0,
'has_rear_camera': 0,
'has_fingerprint': 0,
'is_convertible': 0,
'is_rma_device': 0,
}
}
means configless fileds version 0, 8G memory, 116G storage and has
touchscreen.
"""
decoder = _ConfiglessFieldDecoder(encoded_string)
fields = {
field: decoder(field)
for field in cls.FIELDS
}
return fields
class FeatureList(object):
"""Encode/Decode feature list according to ConfiglessFields.FeatureList"""
def __init__(self, version):
self.features = ConfiglessFields.FEATURE_LIST[version]
def Encode(self, components):
encoded_value = 1
for feature in self.features:
encoded_value <<= 1
encoded_value |= components.get(feature, 0)
return encoded_value
def Decode(self, encoded_value):
feature_count = len(self.features)
if encoded_value >= 2 ** (feature_count + 1):
raise common.HWIDException(
'The given configless fields is invalid. The last field should be a '
'hex value in [0, %s].' %
hex(2 ** (feature_count + 1) - 1).upper().replace('0X', ''))
bin_string = bin(encoded_value).replace('0b', '')[1:]
result = {
self.features[i]: int(bin_string[i])
for i in xrange(len(bin_string))
}
return result
class _ConfiglessFieldGetter(object):
"""Extract value of from BOM / device_info for configless fields."""
def __init__(self, db, bom, device_info, version, is_rma_device):
self._db = db
self._bom = bom
self._device_info = device_info or {}
self._version = version
self._is_rma_device = is_rma_device
self._feature_list = FeatureList(version)
def __call__(self, field_name):
"""Get value of a field."""
return getattr(self, field_name)
@property
def memory(self):
if self.is_rma_device and 'dram' not in self._bom.components:
# We might be generating HWID for RMA spare boards, real DRAM info might
# not be available until the spare board is mounted on device. So it's
# okay to omit this field.
return 0
size_mb = sum(int(self._db.GetComponents('dram')[comp].values['size'])
for comp in self._bom.components['dram'])
return size_mb // 1024
@property
def storage(self):
if self.is_rma_device and 'storage' not in self._bom.components:
# We might be generating HWID for RMA spare boards, real storage info
# might not be available until the spare board is mounted on device. So
# it's okay to omit this field.
return 0
sectors = sum(int(self._db.GetComponents('storage')[comp].values['sectors'])
for comp in self._bom.components['storage'])
# Assume sector size is 512 bytes
return sectors // 2 // 1024 // 1024
@property
def version(self):
return self._version
@property
def is_rma_device(self):
return self._is_rma_device
@property
def feature_list(self):
"""Get feature list encoded string."""
components = self._device_info.get(device_data_constants.KEY_COMPONENT, {})
# Set is_rma_device.
components['is_rma_device'] = self._is_rma_device
return self._feature_list.Encode(components)
class _ConfiglessFieldDecoder(object):
"""Extract value of encoded string for configless fields."""
def __init__(self, encoded_string):
encoded_fields = [int(field, 16) for field in encoded_string.split('-')]
if len(encoded_fields) != len(ConfiglessFields.FIELDS):
raise common.HWIDException(
'The given configless fields %r is invalid. It must have %r fields.' %
(encoded_string, len(ConfiglessFields.FIELDS)))
self._encoded_fields = dict(zip(ConfiglessFields.FIELDS, encoded_fields))
self._feature_list = FeatureList(self._encoded_fields['version'])
def __call__(self, field_name):
"""Get decoded value of a field.
By default, convert encoded hex string to integer.
To override the behavior, create a property of field name
(e.g. `feature_list`).
"""
try:
return getattr(self, field_name)
except Exception:
return self._encoded_fields[field_name]
@property
def feature_list(self):
"""Construct the dict of feature list"""
return self._feature_list.Decode(self._encoded_fields['feature_list'])