hwid: Revises the structure of the RuleContext class.

This CL flattens the `hwid` property in a rule context object into
`database`, `bom` and `mode`.  The final goal is to deprecate the
class `common.HWID`.

TEST=make test
BUG=chromium:689944

Change-Id: Ia1a2dacfbe5c9640e44cd01d935add86a7f7dd12
Reviewed-on: https://chromium-review.googlesource.com/845478
Commit-Ready: Yong Hong <yhong@google.com>
Tested-by: Yong Hong <yhong@google.com>
Reviewed-by: Yong Hong <yhong@google.com>
diff --git a/py/device/info.py b/py/device/info.py
index 5d8fa03..315434e 100755
--- a/py/device/info.py
+++ b/py/device/info.py
@@ -266,7 +266,7 @@
   def hwid_database_version(self):
     """Uses checksum of hwid file as hwid database version."""
     hwid_file_path = self._device.path.join(
-        hwid_utils.DEFAULT_HWID_DATA_PATH, hwid_utils.ProbeProject().upper())
+        hwid_utils.GetDefaultDataPath(), hwid_utils.ProbeProject().upper())
     # TODO(hungte) Support remote DUT.
     return hwid_utils.ComputeDatabaseChecksum(hwid_file_path)
 
diff --git a/py/gooftool/commands.py b/py/gooftool/commands.py
index 97d5359..4704f35 100755
--- a/py/gooftool/commands.py
+++ b/py/gooftool/commands.py
@@ -125,7 +125,7 @@
 
 _hwdb_path_cmd_arg = CmdArg(
     '--hwdb_path', metavar='PATH',
-    default=hwid_utils.DEFAULT_HWID_DATA_PATH,
+    default=hwid_utils.GetDefaultDataPath(),
     help='Path to the HWID database.')
 
 _hwid_status_list_cmd_arg = CmdArg(
diff --git a/py/gooftool/core.py b/py/gooftool/core.py
index 5076df6..42ec20c 100644
--- a/py/gooftool/core.py
+++ b/py/gooftool/core.py
@@ -65,8 +65,8 @@
         load. If not specified, the project name will be detected with
         cros.factory.hwid.ProbeProject(). Used for HWID v3 only.
       hwdb_path: The path to load the project-specific component database from.
-        If not specified, cros.factory.hwid.DEFAULT_HWID_DATA_PATH will be used.
-        Used for HWID v3 only.
+        If not specified, cros.factory.hwid.hwid_utils.GetDefaultDataPath() will
+        be used.  Used for HWID v3 only.
     """
     self._hwid_version = hwid_version
     if hwid_version == 2:
@@ -76,7 +76,7 @@
       self._db_creator = lambda: component_db or self._hardware_db.comp_db
     elif hwid_version == 3:
       self._project = project or hwid_utils.ProbeProject()
-      self._hwdb_path = hwdb_path or hwid_utils.DEFAULT_HWID_DATA_PATH
+      self._hwdb_path = hwdb_path or hwid_utils.GetDefaultDataPath()
       self._db_creator = lambda: Database.LoadFile(
           os.path.join(self._hwdb_path, self._project.upper()))
     else:
diff --git a/py/hwid/v3/bom.py b/py/hwid/v3/bom.py
index 2e76561..19ff203 100644
--- a/py/hwid/v3/bom.py
+++ b/py/hwid/v3/bom.py
@@ -4,6 +4,7 @@
 
 """BOM class for HWID v3 framework."""
 
+import collections
 import copy
 
 import factory_common  # pylint: disable=W0611
@@ -11,6 +12,11 @@
 from cros.factory.utils import schema
 
 
+# A named tuple to store the probed component name and the error if any.
+ProbedComponentResult = collections.namedtuple(
+    'ProbedComponentResult', ['component_name', 'probed_values', 'error'])
+
+
 class BOM(object):
   """A class that holds all the information regarding a BOM.
 
diff --git a/py/hwid/v3/common.py b/py/hwid/v3/common.py
index 3820fbb..b2cd7a7 100644
--- a/py/hwid/v3/common.py
+++ b/py/hwid/v3/common.py
@@ -6,7 +6,6 @@
 
 """Common classes for HWID v3 operation."""
 
-import collections
 import json
 import re
 
@@ -28,10 +27,6 @@
   return (MP_KEY_NAME_PATTERN.search(name) and
           not PRE_MP_KEY_NAME_PATTERN.search(name))
 
-# 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
@@ -149,7 +144,7 @@
     and the self._binary_string is matched, then return it.
     """
     # pylint: disable=W0404
-    from cros.factory.hwid.v3.encoder import BOMToBinaryString
+    from cros.factory.hwid.v3.transformer import BOMToBinaryString
     binary_string = BOMToBinaryString(self.database, self.bom)
     if (self._identity and
         HWID.IsEquivalentBinaryString(self._identity.binary_string,
@@ -165,7 +160,7 @@
     and 45 is the checksum. Compare to binary_string, it is human-trackable.
     """
     # pylint: disable=W0404
-    from cros.factory.hwid.v3.encoder import BinaryStringToEncodedString
+    from cros.factory.hwid.v3.transformer import BinaryStringToEncodedString
     return BinaryStringToEncodedString(self.database, self.binary_string)
 
   def VerifySelf(self):
@@ -179,8 +174,11 @@
     Raises:
       HWIDException on verification error.
     """
-    self.database.VerifyBOM(self.bom)
-    self.database.VerifyEncodedString(self.encoded_string)
+    # pylint: disable=W0404
+    from cros.factory.hwid.v3.transformer import VerifyBOM
+    from cros.factory.hwid.v3.transformer import VerifyEncodedString
+    VerifyBOM(self.database, self.bom)
+    VerifyEncodedString(self.database, self.encoded_string)
 
   def VerifyComponentStatus(self, current_phase=None):
     """Verifies the status of all components.
@@ -317,3 +315,22 @@
       if errors:
         raise HWIDException('MP keys are required in %s, but %s' % (
             current_phase, ' and '.join(errors)))
+
+
+HWID_FORMAT = {
+    HWID.ENCODING_SCHEME.base32: re.compile(
+        r'^([A-Z0-9]+)'                 # group(0): Project
+        r' ('                           # group(1): Entire BOM.
+        r'(?:[A-Z2-7]{4}-)*'            # Zero or more 4-character groups with
+        # dash.
+        r'[A-Z2-7]{1,4}'                # Last group with 1 to 4 characters.
+        r')$'                           # End group(1)
+    ),
+    HWID.ENCODING_SCHEME.base8192: re.compile(
+        r'^([A-Z0-9]+)'                 # group(0): Project
+        r' ('                           # group(1): Entire BOM
+        r'(?:[A-Z2-7][2-9][A-Z2-7]-)*'  # Zero or more 3-character groups with
+        # dash.
+        r'[A-Z2-7][2-9][A-Z2-7]'        # Last group with 3 characters.
+        r')$'                           # End group(1)
+    )}
diff --git a/py/hwid/v3/common_unittest.py b/py/hwid/v3/common_unittest.py
index e76f43d..37fe8b2 100755
--- a/py/hwid/v3/common_unittest.py
+++ b/py/hwid/v3/common_unittest.py
@@ -15,9 +15,9 @@
 from cros.factory.hwid.v3.common import HWIDException
 from cros.factory.hwid.v3.common import IsMPKeyName
 from cros.factory.hwid.v3.database import Database
-from cros.factory.hwid.v3.encoder import Encode
 from cros.factory.hwid.v3 import hwid_utils
 from cros.factory.hwid.v3.identity import Identity
+from cros.factory.hwid.v3.transformer import Encode
 from cros.factory.utils import json_utils
 
 _TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'testdata')
@@ -52,7 +52,7 @@
   def testInvalidInitialize(self):
     bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
                                                   self.results[0])
-    bom = self.database.UpdateComponentsOfBOM(bom, {
+    self.database.UpdateComponentsOfBOM(bom, {
         'keyboard': 'keyboard_us', 'dram': 'dram_0',
         'display_panel': 'display_panel_0'})
     bom.image_id = 2
@@ -76,7 +76,7 @@
   def testVerifySelf(self):
     bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
                                                   self.results[0])
-    bom = self.database.UpdateComponentsOfBOM(bom, {
+    self.database.UpdateComponentsOfBOM(bom, {
         'keyboard': 'keyboard_us', 'dram': 'dram_0',
         'display_panel': 'display_panel_0'})
     bom.image_id = 2
@@ -99,7 +99,7 @@
   def testVerifyProbeResult(self):
     result = self.results[0]
     bom = hwid_utils.GenerateBOMFromProbedResults(self.database, result)
-    bom = self.database.UpdateComponentsOfBOM(bom, {
+    self.database.UpdateComponentsOfBOM(bom, {
         'keyboard': 'keyboard_us', 'dram': 'dram_0',
         'display_panel': 'display_panel_0'})
     bom.image_id = 2
diff --git a/py/hwid/v3/database.py b/py/hwid/v3/database.py
index d5b3881..a5ebea9 100644
--- a/py/hwid/v3/database.py
+++ b/py/hwid/v3/database.py
@@ -10,17 +10,16 @@
 import copy
 import hashlib
 import math
-import pprint
 import re
 
 import factory_common  # pylint: disable=W0611
+from cros.factory.hwid.v3.bom import ProbedComponentResult
 from cros.factory.hwid.v3 import common
 from cros.factory.hwid.v3 import rule
 # Import yaml_tags to decode special YAML tags specific to HWID module.
 from cros.factory.hwid.v3 import yaml_tags  # pylint: disable=W0611
 from cros.factory.hwid.v3 import yaml_wrapper as yaml
 from cros.factory.hwid.v3.base32 import Base32
-from cros.factory.hwid.v3.base8192 import Base8192
 from cros.factory.utils import file_utils
 from cros.factory.utils import schema
 from cros.factory.utils import type_utils
@@ -82,23 +81,6 @@
     rules: A Rules object.
     checksum: The value of the checksum field.
   """
-  _HWID_FORMAT = {
-      common.HWID.ENCODING_SCHEME.base32: re.compile(
-          r'^([A-Z0-9]+)'                 # group(0): Project
-          r' ('                           # group(1): Entire BOM.
-          r'(?:[A-Z2-7]{4}-)*'            # Zero or more 4-character groups with
-          # dash.
-          r'[A-Z2-7]{1,4}'                # Last group with 1 to 4 characters.
-          r')$'                           # End group(1)
-      ),
-      common.HWID.ENCODING_SCHEME.base8192: re.compile(
-          r'^([A-Z0-9]+)'                 # group(0): Project
-          r' ('                           # group(1): Entire BOM
-          r'(?:[A-Z2-7][2-9][A-Z2-7]-)*'  # Zero or more 3-character groups with
-          # dash.
-          r'[A-Z2-7][2-9][A-Z2-7]'        # Last group with 3 characters.
-          r')$'                           # End group(1)
-      )}
 
   def __init__(self, project, encoding_patterns, image_id, pattern,
                encoded_fields, components, rules, checksum):
@@ -267,32 +249,26 @@
       bom: A BOM object to update.
       updated_components: A dict of component classes to component names
           indicating the set of components to update.
-
-    Returns:
-      A BOM object with updated components and encoded fields.
     """
-    result = bom.Duplicate()
     for comp_cls, comp_name in updated_components.iteritems():
       new_probed_result = []
       if comp_name is None:
-        new_probed_result.append(common.ProbedComponentResult(
+        new_probed_result.append(ProbedComponentResult(
             None, None, common.MISSING_COMPONENT_ERROR(comp_cls)))
       else:
         comp_name = type_utils.MakeList(comp_name)
         for name in comp_name:
           comp_attrs = self.components.GetComponentAttributes(comp_cls, name)
-          new_probed_result.append(common.ProbedComponentResult(
+          new_probed_result.append(ProbedComponentResult(
               name, comp_attrs['values'], None))
       # Update components data of the duplicated BOM.
-      result.components[comp_cls] = new_probed_result
+      bom.components[comp_cls] = new_probed_result
 
     # Re-calculate all the encoded index of each encoded field.
-    result.encoded_fields = {}
+    bom.encoded_fields = {}
     for field in self.encoded_fields:
-      result.encoded_fields[field] = self.GetFieldIndexFromProbedComponents(
-          field, result.components)
-
-    return result
+      bom.encoded_fields[field] = self.GetFieldIndexFromProbedComponents(
+          field, bom.components)
 
   def GetFieldIndexFromProbedComponents(self, encoded_field, probed_components):
     """Gets the encoded index of the specified encoded field by matching
@@ -382,170 +358,6 @@
           result[comp_cls].append(new_attr)
     return result
 
-  def VerifyBinaryString(self, binary_string):
-    """Verifies the binary string.
-
-    Args:
-      binary_string: The binary string to verify.
-
-    Raises:
-      HWIDException if verification fails.
-    """
-    if set(binary_string) - set('01'):
-      raise common.HWIDException('Invalid binary string: %r' % binary_string)
-
-    if '1' not in binary_string:
-      raise common.HWIDException('Binary string %r does not have stop bit set',
-                                 binary_string)
-    # Truncate trailing 0s.
-    string_without_paddings = binary_string[:binary_string.rfind('1') + 1]
-
-    image_id = self.pattern.GetImageIdFromBinaryString(binary_string)
-    if len(string_without_paddings) > self.pattern.GetTotalBitLength(image_id):
-      raise common.HWIDException('Invalid bit string length of %r. Expected '
-                                 'length <= %d, got length %d' %
-                                 (binary_string,
-                                  self.pattern.GetTotalBitLength(image_id),
-                                  len(string_without_paddings)))
-
-  def VerifyEncodedStringFormat(self, encoded_string):
-    """Verifies that the format of the given encoded string.
-
-    Checks that the string matches either base32 or base8192 format.
-
-    Args:
-      encoded_string: The encoded string to verify.
-
-    Raises:
-      HWIDException if verification fails.
-    """
-    if not any(hwid_format.match(encoded_string) for hwid_format in
-               self._HWID_FORMAT.itervalues()):
-      raise common.HWIDException(
-          'HWID string %r is neither base32 nor base8192 encoded' %
-          encoded_string)
-
-  def VerifyEncodedString(self, encoded_string):
-    """Verifies the given encoded string.
-
-    Args:
-      encoded_string: The encoded string to verify.
-
-    Raises:
-      HWIDException if verification fails.
-    """
-    try:
-      image_id = self.pattern.GetImageIdFromEncodedString(encoded_string)
-      encoding_scheme = self.pattern.GetPatternByImageId(
-          image_id)['encoding_scheme']
-      project, bom_checksum = Database._HWID_FORMAT[encoding_scheme].findall(
-          encoded_string)[0]
-    except IndexError:
-      raise common.HWIDException(
-          'Invalid HWID string format: %r' % encoded_string)
-    if len(bom_checksum) < 2:
-      raise common.HWIDException(
-          'Length of encoded string %r is less than 2 characters' %
-          bom_checksum)
-    if project != self.project.upper():
-      raise common.HWIDException('Invalid project name: %r' % project)
-    # Verify the checksum
-    stripped = encoded_string.replace('-', '')
-    hwid = stripped[:-2]
-    checksum = stripped[-2:]
-    if encoding_scheme == common.HWID.ENCODING_SCHEME.base32:
-      expected_checksum = Base32.Checksum(hwid)
-    elif encoding_scheme == common.HWID.ENCODING_SCHEME.base8192:
-      expected_checksum = Base8192.Checksum(hwid)
-    if checksum != expected_checksum:
-      raise common.HWIDException('Checksum of %r mismatch (expected %r)' % (
-          encoded_string, expected_checksum))
-
-  def VerifyBOM(self, bom, probeable_only=False):
-    """Verifies the data contained in the given BOM object matches the settings
-    and definitions in the database.
-
-    Because the components for each image ID might be different, for example a
-    component might be removed in later build. We only verify the components in
-    the target image ID, not all components listed in the database.
-
-    When the BOM is decoded by HWID string, it would contain the information of
-    every component recorded in the pattern. But if the BOM object is created by
-    the probed result, it does not contain the unprobeable component before
-    evaluating the rule. We should verify the probeable components only.
-
-    Args:
-      bom: The BOM object to verify.
-      probeable_only: True to verify the probeable component only.
-
-    Raises:
-      HWIDException if verification fails.
-    """
-    if bom.project != self.project:
-      raise common.HWIDException('Invalid project name. Expected %r, got %r' %
-                                 (self.project, bom.project))
-
-    if bom.encoding_pattern_index not in self.encoding_patterns:
-      raise common.HWIDException('Invalid encoding pattern: %r' %
-                                 bom.encoding_pattern_index)
-    if bom.image_id not in self.image_id:
-      raise common.HWIDException('Invalid image id: %r' % bom.image_id)
-
-    # All the classes encoded in the pattern should exist in BOM.
-    # Ignore unprobeable components if probeable_only is True.
-    missing_comp = []
-    expected_encoded_fields = self.pattern.GetFieldNames(bom.image_id)
-    for comp_cls in self.GetActiveComponents(bom.image_id):
-      if (comp_cls not in bom.components and
-          (comp_cls in self.components.probeable or not probeable_only)):
-        missing_comp.append(comp_cls)
-    if missing_comp:
-      raise common.HWIDException('Missing component classes: %r',
-                                 ', '.join(sorted(missing_comp)))
-
-    bom_encoded_fields = type_utils.MakeSet(bom.encoded_fields.keys())
-    db_encoded_fields = type_utils.MakeSet(expected_encoded_fields)
-    # Every encoded field defined in the database must present in BOM.
-    if db_encoded_fields - bom_encoded_fields:
-      raise common.HWIDException('Missing encoded fields in BOM: %r',
-                                 ', '.join(sorted(db_encoded_fields -
-                                                  bom_encoded_fields)))
-
-    # All the probeable component values in the BOM should exist in the
-    # database.
-    unknown_values = []
-    for comp_cls, probed_values in bom.components.iteritems():
-      if comp_cls not in self.components.probeable:
-        continue
-      for element in probed_values:
-        probed_values = element.probed_values
-        if probed_values is None:
-          continue
-        found_comps = self.components.MatchComponentsFromValues(
-            comp_cls, probed_values, include_default=True)
-        if not found_comps:
-          unknown_values.append('%s:%s' % (comp_cls, pprint.pformat(
-              probed_values, indent=0, width=1024)))
-    if unknown_values:
-      raise common.HWIDException('Unknown component values: %r' %
-                                 ', '.join(sorted(unknown_values)))
-
-    # All the encoded index should exist in the database.
-    invalid_fields = []
-    for field_name in expected_encoded_fields:
-      # Ignore the field containing unprobeable component.
-      if probeable_only and not all(
-          [comp_cls in self.components.probeable
-           for comp_cls in self.encoded_fields[field_name][0].keys()]):
-        continue
-      index = bom.encoded_fields[field_name]
-      if index is None or index not in self.encoded_fields[field_name]:
-        invalid_fields.append(field_name)
-
-    if invalid_fields:
-      raise common.HWIDException('Encoded fields %r have unknown indices' %
-                                 ', '.join(sorted(invalid_fields)))
-
   def VerifyComponents(self, bom, comp_list=None):
     """Given a list of component classes, verify that the probed components of
     all the component classes in the list are valid components in the database.
diff --git a/py/hwid/v3/database_unittest.py b/py/hwid/v3/database_unittest.py
index d5d80a8..d2e82e9 100755
--- a/py/hwid/v3/database_unittest.py
+++ b/py/hwid/v3/database_unittest.py
@@ -13,8 +13,9 @@
 import factory_common  # pylint: disable=W0611
 
 from cros.factory.hwid.v3 import common
-from cros.factory.hwid.v3.common import HWIDException, ProbedComponentResult
-from cros.factory.hwid.v3.database import Database, Components
+from cros.factory.hwid.v3.common import HWIDException
+from cros.factory.hwid.v3.database import Components
+from cros.factory.hwid.v3.database import Database
 from cros.factory.hwid.v3 import hwid_utils
 from cros.factory.hwid.v3.rule import Value
 from cros.factory.hwid.v3 import yaml_wrapper as yaml
@@ -97,9 +98,6 @@
                                                        probed_value))])
     self.assertEquals(bom.encoded_fields['cellular_field'], None)
 
-  def testVerifyBOMWithDefault(self):
-    self.assertEquals(None, self.database.VerifyBOM(self.boms[6], True))
-
 
 class DatabaseTest(unittest.TestCase):
 
@@ -197,28 +195,29 @@
 
   def testUpdateComponentsOfBOM(self):
     bom = self.boms[0]
-    new_bom = self.database.UpdateComponentsOfBOM(
-        bom, {'keyboard': 'keyboard_gb'})
+    new_bom = bom.Duplicate()
+    self.database.UpdateComponentsOfBOM(new_bom, {'keyboard': 'keyboard_gb'})
     self.assertEquals([('keyboard_gb', None, None)],
                       new_bom.components['keyboard'])
     self.assertEquals(1, new_bom.encoded_fields['keyboard'])
-    new_bom = self.database.UpdateComponentsOfBOM(
-        bom, {'audio_codec': ['codec_0', 'hdmi_0']})
+    new_bom = bom.Duplicate()
+    self.database.UpdateComponentsOfBOM(
+        new_bom, {'audio_codec': ['codec_0', 'hdmi_0']})
     self.assertEquals(
         [('codec_0', {'compact_str': Value('Codec 0')}, None),
          ('hdmi_0', {'compact_str': Value('HDMI 0')}, None)],
         new_bom.components['audio_codec'])
     self.assertEquals(0, new_bom.encoded_fields['audio_codec'])
-    new_bom = self.database.UpdateComponentsOfBOM(
-        bom, {'cellular': 'cellular_0'})
+    new_bom = bom.Duplicate()
+    self.database.UpdateComponentsOfBOM(new_bom, {'cellular': 'cellular_0'})
     self.assertEquals([('cellular_0',
                         {'idVendor': Value('89ab'), 'idProduct': Value('abcd'),
                          'name': Value('Cellular Card')},
                         None)],
                       new_bom.components['cellular'])
     self.assertEquals(1, new_bom.encoded_fields['cellular'])
-    new_bom = self.database.UpdateComponentsOfBOM(
-        bom, {'cellular': None})
+    new_bom = bom.Duplicate()
+    self.database.UpdateComponentsOfBOM(new_bom, {'cellular': None})
     self.assertEquals([(None, None, "Missing 'cellular' component")],
                       new_bom.components['cellular'])
     self.assertEquals(0, new_bom.encoded_fields['cellular'])
@@ -286,81 +285,6 @@
     self.assertEquals({'cellular': None},
                       self.database._GetAttributesByIndex('cellular', 0))
 
-  def testVerifyBinaryString(self):
-    self.assertEquals(
-        None, self.database.VerifyBinaryString('0000000000111010000011000'))
-    self.assertRaisesRegexp(
-        HWIDException, r'Invalid binary string: .*',
-        self.database.VerifyBinaryString, '020001010011011011000')
-    self.assertRaisesRegexp(
-        HWIDException, r'Binary string .* does not have stop bit set',
-        self.database.VerifyBinaryString, '00000')
-    self.assertRaisesRegexp(
-        HWIDException, r'Invalid bit string length',
-        self.database.VerifyBinaryString, '000000000010100110110111000')
-
-  def testVerifyEncodedString(self):
-    self.assertEquals(
-        None, self.database.VerifyEncodedString('CHROMEBOOK AW3L-M7I7-V'))
-    self.assertRaisesRegexp(
-        HWIDException, r'Invalid HWID string format',
-        self.database.VerifyEncodedString, 'AW3L-M7I5-4')
-    self.assertRaisesRegexp(
-        HWIDException, r'Length of encoded string .* is less than 2 characters',
-        self.database.VerifyEncodedString, 'FOO A')
-    self.assertRaisesRegexp(
-        HWIDException, r'Invalid project name',
-        self.database.VerifyEncodedString, 'FOO AW3L-M7IK-W')
-    self.assertRaisesRegexp(
-        HWIDException, r'Checksum of .* mismatch',
-        self.database.VerifyEncodedString, 'CHROMEBOOK AW3L-M7IA-B')
-
-  def testVerifyBOM(self):
-    # Before evaluating rule to update the unprobeable component, we only verify
-    # the probeable components.
-    bom = self.boms[0]
-    self.assertEquals(None, self.database.VerifyBOM(bom, True))
-
-    original_value = bom.project
-    bom.project = 'FOO'
-    with self.assertRaisesRegexp(HWIDException,
-                                 r'Invalid project name. Expected .*, got .*'):
-      self.database.VerifyBOM(bom, True)
-    bom.project = original_value
-
-    original_value = bom.encoding_pattern_index
-    bom.encoding_pattern_index = 2
-    with self.assertRaisesRegexp(HWIDException, r'Invalid encoding pattern'):
-      self.database.VerifyBOM(bom, True)
-    bom.encoding_pattern_index = original_value
-
-    original_value = bom.image_id
-    bom.image_id = 6
-    with self.assertRaisesRegexp(HWIDException, r'Invalid image id: .*'):
-      self.database.VerifyBOM(bom, True)
-    bom.image_id = original_value
-
-    original_value = bom.encoded_fields['cpu']
-    bom.encoded_fields['cpu'] = 8
-    with self.assertRaisesRegexp(HWIDException,
-                                 r'Encoded fields .* have unknown indices'):
-      self.database.VerifyBOM(bom, True)
-    bom.encoded_fields['cpu'] = original_value
-
-    original_value = bom.components['cpu']
-    bom.components['cpu'] = [ProbedComponentResult(
-        'cpu', {'name': Value('foo'), 'cores': Value('4')}, None)]
-    with self.assertRaisesRegexp(HWIDException, r'Unknown component values:.*'):
-      self.database.VerifyBOM(bom, True)
-    bom.components['cpu'] = original_value
-
-    original_value = bom.encoded_fields['cpu']
-    bom.encoded_fields.pop('cpu')
-    with self.assertRaisesRegexp(HWIDException,
-                                 r'Missing encoded fields in BOM: .*'):
-      self.database.VerifyBOM(bom, True)
-    bom.encoded_fields['cpu'] = original_value
-
   def testVerifyComponents(self):
     self.maxDiff = None
     bom = self.boms[0]
diff --git a/py/hwid/v3/decoder.py b/py/hwid/v3/decoder.py
deleted file mode 100644
index b485f3e..0000000
--- a/py/hwid/v3/decoder.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# Copyright 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.
-
-"""Implementation of HWID v3 decoder."""
-
-import collections
-import factory_common  # pylint: disable=W0611
-
-from cros.factory.hwid.v3.bom import BOM
-from cros.factory.hwid.v3 import common
-from cros.factory.hwid.v3.base32 import Base32
-from cros.factory.hwid.v3.base8192 import Base8192
-from cros.factory.hwid.v3.identity import Identity
-
-_Decoder = {
-    common.HWID.ENCODING_SCHEME.base32: Base32,
-    common.HWID.ENCODING_SCHEME.base8192: Base8192
-}
-
-
-def BinaryStringToBOM(database, binary_string):
-  """Decodes the given binary string to a BOM object.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for decoding.
-    binary_string: A binary string.
-
-  Returns:
-    A BOM object
-  """
-  database.VerifyBinaryString(binary_string)
-  stripped_binary_string = binary_string[:binary_string.rfind('1')]
-
-  project = database.project
-  encoding_pattern = int(stripped_binary_string[0], 2)
-  image_id = database.pattern.GetImageIdFromBinaryString(binary_string)
-
-  # Construct the encoded fields dict.
-  encoded_fields = collections.defaultdict(int)
-  for field_name in database.pattern.GetFieldNames(image_id):
-    encoded_fields[field_name] = 0
-  bit_mapping = database.pattern.GetBitMapping(
-      image_id=image_id, binary_string_length=len(stripped_binary_string))
-  for i, (field, bit_offset) in bit_mapping.iteritems():
-    if i >= len(stripped_binary_string):
-      break
-    encoded_fields[field] += int(stripped_binary_string[i], 2) << bit_offset
-
-  # Check that all the encoded field indices are valid.
-  expected_encoded_fields = database.pattern.GetFieldNames(image_id)
-  missing_fields = set(expected_encoded_fields) - set(encoded_fields.keys())
-  if missing_fields:
-    raise common.HWIDException('Index of the fields are missing: %r' %
-                               list(missing_fields))
-  for field in encoded_fields:
-    if encoded_fields[field] not in database.encoded_fields[field]:
-      raise common.HWIDException('Invalid encoded field index: {%r: %r}' %
-                                 (field, encoded_fields[field]))
-
-  # Construct the components dict.
-  components = collections.defaultdict(list)
-  for field, index in encoded_fields.iteritems():
-    # pylint: disable=W0212
-    attr_dict = database._GetAttributesByIndex(field, index)
-    for comp_cls, attr_list in attr_dict.iteritems():
-      if attr_list is None:
-        components[comp_cls].append(common.ProbedComponentResult(
-            None, None, common.MISSING_COMPONENT_ERROR(comp_cls)))
-      else:
-        for attrs in attr_list:
-          components[comp_cls].append(common.ProbedComponentResult(
-              attrs['name'], attrs['values'], None))
-
-  return BOM(project, encoding_pattern, image_id, components, encoded_fields)
-
-
-def EncodedStringToBinaryString(database, encoded_string):
-  """Decodes the given encoded HWID string to a binary string.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for decoding.
-    encoded_string: An encoded string (with or without dashed).
-
-  Returns:
-    A binary string.
-  """
-  database.VerifyEncodedStringFormat(encoded_string)
-  image_id = database.pattern.GetImageIdFromEncodedString(encoded_string)
-  encoding_scheme = database.pattern.GetPatternByImageId(
-      image_id)['encoding_scheme']
-  database.VerifyEncodedString(encoded_string)
-  _, hwid_string = encoded_string.split(' ')
-  hwid_string = hwid_string.replace('-', '')
-  return _Decoder[encoding_scheme].Decode(
-      hwid_string)[:-_Decoder[encoding_scheme].CHECKSUM_SIZE].rstrip('0')
-
-
-def Decode(database, encoded_string, mode=common.HWID.OPERATION_MODE.normal):
-  """Decodes the given encoded string to a HWID object.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for decoding.
-    encoded_string: An encoded string.
-    mode: The operation mode of the generated HWID object. Valid values are:
-        ('normal', 'rma')
-
-  Returns:
-    A HWID object which contains the BOM, the binary string, and the encoded
-    string derived from the given encoded string.
-  """
-  binary_string = EncodedStringToBinaryString(database, encoded_string)
-  identity = Identity(database.project, binary_string, encoded_string)
-  bom = BinaryStringToBOM(database, binary_string)
-  return common.HWID(database, bom=bom, identity=identity, mode=mode)
diff --git a/py/hwid/v3/decoder_unittest.py b/py/hwid/v3/decoder_unittest.py
deleted file mode 100755
index 3e515ae..0000000
--- a/py/hwid/v3/decoder_unittest.py
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright 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.
-
-import os
-import unittest
-import factory_common  # pylint: disable=W0611
-
-from cros.factory.hwid.v3.common import HWIDException
-from cros.factory.hwid.v3.database import Database
-from cros.factory.hwid.v3.decoder import EncodedStringToBinaryString
-from cros.factory.hwid.v3.decoder import BinaryStringToBOM, Decode
-from cros.factory.hwid.v3 import hwid_utils
-from cros.factory.hwid.v3.rule import Value
-from cros.factory.utils import json_utils
-
-_TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'testdata')
-
-
-class DecoderTest(unittest.TestCase):
-
-  def setUp(self):
-    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
-                                                   'test_db.yaml'))
-    self.results = json_utils.LoadFile(
-        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
-    self.expected_components_from_db = {
-        'audio_codec': [('codec_1', {'compact_str': Value('Codec 1')}, None),
-                        ('hdmi_1', {'compact_str': Value('HDMI 1')}, None)],
-        'battery': [('battery_huge',
-                     {'tech': Value('Battery Li-ion'),
-                      'size': Value('10000000')},
-                     None)],
-        'bluetooth': [('bluetooth_0',
-                       {'idVendor': Value('0123'), 'idProduct': Value('abcd'),
-                        'bcd': Value('0001')},
-                       None)],
-        'cellular': [(None, None, "Missing 'cellular' component")],
-        'cpu': [('cpu_5',
-                 {'name': Value('CPU @ 2.80GHz'), 'cores': Value('4')},
-                 None)],
-        'display_panel': [('display_panel_0', None, None)],
-        'dram': [('dram_0',
-                  {'vendor': Value('DRAM 0'), 'size': Value('4G')},
-                  None)],
-        'ec_flash_chip': [('ec_flash_chip_0',
-                           {'compact_str': Value('EC Flash Chip')},
-                           None)],
-        'embedded_controller': [('embedded_controller_0',
-                                 {'compact_str': Value('Embedded Controller')},
-                                 None)],
-        'flash_chip': [('flash_chip_0',
-                        {'compact_str': Value('Flash Chip')},
-                        None)],
-        'hash_gbb': [('hash_gbb_0',
-                      {'compact_str': Value('gv2#hash_gbb_0')},
-                      None)],
-        'key_recovery': [('key_recovery_0',
-                          {'compact_str': Value('kv3#key_recovery_0')},
-                          None)],
-        'key_root': [('key_root_0',
-                      {'compact_str': Value('kv3#key_root_0')},
-                      None)],
-        'keyboard': [('keyboard_us', None, None)],
-        'ro_ec_firmware': [('ro_ec_firmware_0',
-                            {'compact_str': Value('ev2#ro_ec_firmware_0')},
-                            None)],
-        'ro_main_firmware': [('ro_main_firmware_0',
-                              {'compact_str': Value('mv2#ro_main_firmware_0')},
-                              None)],
-        'storage': [('storage_0',
-                     {'type': Value('SSD'), 'size': Value('16G'),
-                      'serial': Value(r'^#123\d+$', is_re=True)},
-                     None)],
-        'video': [('camera_0',
-                   {'idVendor': Value('4567'), 'idProduct': Value('abcd'),
-                    'type': Value('webcam')},
-                   None)]}
-
-  def _CheckBOM(self, reference_bom, bom):
-    """Check the BOM decoded from HWID is equivalent to the reference BOM.
-
-    reference_bom (generated from probed result) has all information of
-    encoded_fields and component, but bom (generated from binary string) only
-    has the information of the pattern. Therefore we check bom is the subset
-    of the reference_bom.
-    """
-    self.assertEquals(reference_bom.project, bom.project)
-    self.assertEquals(reference_bom.encoding_pattern_index,
-                      bom.encoding_pattern_index)
-    self.assertEquals(reference_bom.image_id, bom.image_id)
-    for comp_cls in bom.encoded_fields:
-      self.assertEquals(reference_bom.encoded_fields[comp_cls],
-                        bom.encoded_fields[comp_cls])
-    for comp_cls in bom.components:
-      self.assertEquals(self.expected_components_from_db[comp_cls],
-                        bom.components[comp_cls])
-
-  def testEncodedStringToBinaryString(self):
-    self.assertEquals('0000000000111010000011',
-                      EncodedStringToBinaryString(
-                          self.database, 'CHROMEBOOK AA5A-Y6L'))
-    self.assertEquals('0001000000111010000011',
-                      EncodedStringToBinaryString(
-                          self.database, 'CHROMEBOOK C2H-I3Q-A6Q'))
-    self.assertEquals('1000000000111010000011',
-                      EncodedStringToBinaryString(
-                          self.database, 'CHROMEBOOK QA5A-YCJ'))
-
-  def testBinaryStringToBOM(self):
-    reference_bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
-                                                            self.results[0])
-    reference_bom = self.database.UpdateComponentsOfBOM(reference_bom, {
-        'keyboard': 'keyboard_us',
-        'display_panel': 'display_panel_0'})
-    bom = BinaryStringToBOM(self.database, '0000000000111010000011')
-    self._CheckBOM(reference_bom, bom)
-
-    bom = BinaryStringToBOM(self.database, '0000000001111010000011')
-    self.assertEquals(1, bom.encoded_fields['firmware'])
-    self.assertEquals(2, BinaryStringToBOM(
-        self.database, '0001000000111010000011').image_id)
-    self.assertEquals(1, BinaryStringToBOM(
-        self.database, '1000000000111010000011').encoding_pattern_index)
-    self.assertRaisesRegexp(
-        HWIDException, r"Invalid encoded field index: {'cpu': 6}",
-        BinaryStringToBOM, self.database, '0000000000111000010011')
-
-  def testIncompleteBinaryStringToBOM(self):
-    # The latest pattern in the test database has 16 bits (plus the image ID and
-    # the stop bit), with the last three bits being one two-bit storage_field,
-    # and one one-bit cpu_field.
-
-    # Test with 21 bits here. This should be regarded as a valid binary string
-    # that was generated before we extended cpu_field.
-    bom = BinaryStringToBOM(
-        self.database,
-        '00000'         # image ID
-        '0000111101000'  # 13 bits, up through second cpu_field
-        '00'            # storage_field
-        '1')            # stop bit
-    # Bit 15 is 1, which is the first cpu_field. The cpu_field should be decoded
-    # as b01 = 1.
-    self.assertEquals(1, bom.encoded_fields['cpu'])
-    self.assertEquals(0, bom.encoded_fields['storage'])
-
-    # Test with 20 bits here. This should be regarded as an incomplete bit chunk
-    # for storage_field, i.e. storage_field was previously one bit (and some
-    # HWIDs were generated) but it has since been extended to two bits.
-    bom = BinaryStringToBOM(
-        self.database,
-        '00000'         # image ID
-        '0000111101000'  # 12 bits, up through second cpu_field
-        '1'             # the incomplete storage_field chunk
-        '1')            # stop bit
-    # The cpu_field should still be b01 = 1.
-    self.assertEquals(1, bom.encoded_fields['cpu'])
-    # The 1 in the incomplete two-bit storage field should be decoded as b1 = 1
-    # instead of b10 = 2.
-    self.assertEquals(1, bom.encoded_fields['storage'])
-
-  def testDecode(self):
-    reference_bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
-                                                            self.results[0])
-    reference_bom = self.database.UpdateComponentsOfBOM(reference_bom, {
-        'keyboard': 'keyboard_us', 'dram': 'dram_0',
-        'display_panel': 'display_panel_0'})
-    hwid = Decode(self.database, 'CHROMEBOOK AA5A-Y6L')
-    self.assertEquals('0000000000111010000011', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK AA5A-Y6L', hwid.encoded_string)
-    self._CheckBOM(reference_bom, hwid.bom)
-
-    hwid = Decode(self.database, 'CHROMEBOOK C2H-I3Q-A6Q')
-    self.assertEquals('0001000000111010000011', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK C2H-I3Q-A6Q', hwid.encoded_string)
-    reference_bom.image_id = 2
-    self._CheckBOM(reference_bom, hwid.bom)
-
-  def testPreviousVersionOfEncodedString(self):
-    bom = BinaryStringToBOM(self.database, '000000000011101000001')
-    self.assertEquals(1, bom.encoded_fields['cpu'])
-    hwid = Decode(self.database, 'CHROMEBOOK AA5A-Q7Z')
-    self.assertEquals('000000000011101000001', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK AA5A-Q7Z', hwid.encoded_string)
-    self.assertEquals(1, hwid.bom.encoded_fields['cpu'])
-
-  def testDecodeRegion(self):
-    db = Database.LoadFile(
-        os.path.join(_TEST_DATA_PATH, 'test_db_regions.yaml'))
-    hwid = Decode(db, 'CHROMEBOOK A25-Q22')
-    # The BOM should load 'us' region from the probe result (numeric_id=29).)
-    self.assertEquals(29, hwid.bom.encoded_fields['region_field'])
-    self.assertEquals(
-        [('us', {'region_code': Value('us')}, None)],
-        hwid.bom.components['region'])
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/py/hwid/v3/encoder.py b/py/hwid/v3/encoder.py
deleted file mode 100644
index 5385bed..0000000
--- a/py/hwid/v3/encoder.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright 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.
-
-"""Implementation of HWID v3 encoder."""
-
-import factory_common  # pylint: disable=W0611
-
-from cros.factory.hwid.v3 import common
-from cros.factory.hwid.v3.base32 import Base32
-from cros.factory.hwid.v3.base8192 import Base8192
-
-_Encoder = {
-    common.HWID.ENCODING_SCHEME.base32: Base32,
-    common.HWID.ENCODING_SCHEME.base8192: Base8192
-}
-
-
-def BOMToBinaryString(database, bom):
-  """Encodes the given BOM object to a binary string.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for encoding.
-
-  Returns:
-    A binary string.
-  """
-  database.VerifyBOM(bom)
-  bit_length = database.pattern.GetTotalBitLength(bom.image_id)
-  binary_list = bit_length * [0]
-
-  # Fill in header.
-  binary_list[0] = bom.encoding_pattern_index
-  for i in xrange(1, 5):
-    binary_list[i] = (bom.image_id >> (4 - i)) & 1
-  # Fill in each bit.
-  bit_mapping = database.pattern.GetBitMapping(bom.image_id)
-  for index, (field, bit_offset) in bit_mapping.iteritems():
-    binary_list[index] = (bom.encoded_fields[field] >> bit_offset) & 1
-  # Set stop bit.
-  binary_list[bit_length - 1] = 1
-  return ''.join(['%d' % bit for bit in binary_list])
-
-
-def BinaryStringToEncodedString(database, binary_string):
-  """Encodes the given binary string to a encoded string.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for encoding.
-    binary_string: A string of '0's and '1's.
-
-  Returns:
-    An encoded string with project name, base32-encoded HWID, and checksum.
-  """
-  database.VerifyBinaryString(binary_string)
-  image_id = database.pattern.GetImageIdFromBinaryString(binary_string)
-  encoding_scheme = database.pattern.GetPatternByImageId(
-      image_id)['encoding_scheme']
-  encoder = _Encoder[encoding_scheme]
-  encoded_string = encoder.Encode(binary_string)
-  # Make project name part of the checksum.
-  encoded_string += encoder.Checksum(
-      database.project.upper() + ' ' + encoded_string)
-  # Insert dashes to increase readibility.
-  encoded_string = ('-'.join(
-      [encoded_string[i:i + encoder.DASH_INSERTION_WIDTH]
-       for i in xrange(0, len(encoded_string), encoder.DASH_INSERTION_WIDTH)]))
-  return database.project.upper() + ' ' + encoded_string
-
-
-def Encode(database, bom, mode=common.HWID.OPERATION_MODE.normal,
-           skip_check=False):
-  """Encodes all the given BOM object.
-
-  Args:
-    database: A Database object that is used to provide device-specific
-        information for encoding.
-    bom: A BOM object.
-    mode: The operation mode of the generated HWID object. Valid values are:
-        ('normal', 'rma')
-    skip_check: Whether to skip HWID verification checks. Set to True when
-        generating HWID skeleton objects for further processing.
-
-  Returns:
-    A HWID object which contains the BOM, the binary string, and the encoded
-    string derived from the given BOM object.
-  """
-  hwid = common.HWID(database, bom=bom, mode=mode, skip_check=skip_check)
-  return hwid
diff --git a/py/hwid/v3/encoder_unittest.py b/py/hwid/v3/encoder_unittest.py
deleted file mode 100755
index c6235af..0000000
--- a/py/hwid/v3/encoder_unittest.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright 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.
-
-import copy
-import os
-import unittest
-import factory_common  # pylint: disable=W0611
-
-from cros.factory.hwid.v3.common import HWIDException
-from cros.factory.hwid.v3.database import Database
-from cros.factory.hwid.v3.encoder import BinaryStringToEncodedString
-from cros.factory.hwid.v3.encoder import BOMToBinaryString
-from cros.factory.hwid.v3.encoder import Encode
-from cros.factory.hwid.v3 import hwid_utils
-from cros.factory.utils import json_utils
-
-_TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'testdata')
-
-
-class EncoderTest(unittest.TestCase):
-
-  def setUp(self):
-    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
-                                                   'test_db.yaml'))
-    self.results = json_utils.LoadFile(
-        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
-
-  def testBOMToBinaryString(self):
-    bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
-                                                  self.results[0])
-    # Manually set unprobeable components.
-    bom = self.database.UpdateComponentsOfBOM(bom, {
-        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
-    self.assertEquals(
-        '0000000000111010000011', BOMToBinaryString(self.database, bom))
-    # Change firmware's encoded index to 1.
-    mocked_bom = self.database.UpdateComponentsOfBOM(
-        bom, {'ro_main_firmware': 'ro_main_firmware_1'})
-    self.assertEquals(
-        '0000000001111010000011', BOMToBinaryString(self.database, mocked_bom))
-    # Change image id to 2.
-    mocked_bom.image_id = 2
-    self.assertEquals(
-        '0001000001111010000011', BOMToBinaryString(self.database, mocked_bom))
-    # Change encoding pattern index to 1.
-    mocked_bom.encoding_pattern_index = 1
-    self.assertEquals(
-        '1001000001111010000011', BOMToBinaryString(self.database, mocked_bom))
-
-  def testBinaryStringToEncodedString(self):
-    self.assertEquals('CHROMEBOOK A5AU-LU',
-                      BinaryStringToEncodedString(
-                          self.database, '000001110100000101'))
-    self.assertEquals('CHROMEBOOK C9I-F4N',
-                      BinaryStringToEncodedString(
-                          self.database, '000101110100000101'))
-
-  def testEncode(self):
-    bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
-                                                  self.results[0])
-    # Manually set unprobeable components.
-    bom = self.database.UpdateComponentsOfBOM(bom, {
-        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
-    bom.image_id = 0
-    hwid = Encode(self.database, bom)
-    self.assertEquals('0000000000111010000011', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK AA5A-Y6L', hwid.encoded_string)
-
-    bom.image_id = 2
-    hwid = Encode(self.database, bom)
-    self.assertEquals('0001000000111010000011', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK C2H-I3Q-A6Q', hwid.encoded_string)
-
-  def testEncodeError(self):
-    # Missing required component 'dram'.
-    mock_results = copy.deepcopy(self.results[0])
-    mock_results.pop('dram')
-    bom = hwid_utils.GenerateBOMFromProbedResults(self.database, mock_results)
-    bom = self.database.UpdateComponentsOfBOM(bom, {
-        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
-    self.assertRaisesRegexp(
-        HWIDException, "Encoded fields 'dram' have unknown indices",
-        Encode, self.database, bom)
-
-    # Unsupported probe values of component 'dram'.
-    mock_results = copy.deepcopy(self.results[0])
-    mock_results['dram'] = {'generic': [{'vendor': 'FOO', 'size': '4G'}]}
-    bom = hwid_utils.GenerateBOMFromProbedResults(self.database, mock_results)
-    bom = self.database.UpdateComponentsOfBOM(bom, {
-        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
-    self.assertRaisesRegexp(
-        HWIDException, "Unknown component values: "
-        "\"dram:{'size': '4G', 'vendor': 'FOO'}\"",
-        Encode, self.database, bom)
-
-  def testEncodeRegion(self):
-    db = Database.LoadFile(
-        os.path.join(_TEST_DATA_PATH, 'test_db_regions.yaml'))
-    bom = hwid_utils.GenerateBOMFromProbedResults(db, self.results[5])
-    # The BOM should load 'us' region from the probe result (numeric_id=29).)
-    self.assertEquals(29, bom.encoded_fields['region_field'])
-    hwid = Encode(db, bom)
-    # The encoder should encode field index 29 into the region field.
-    self.assertEquals('00000000111011', hwid.binary_string)
-    self.assertEquals('CHROMEBOOK A25-Q22', hwid.encoded_string)
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/py/hwid/v3/hwid_cmdline.py b/py/hwid/v3/hwid_cmdline.py
index 8548dd4..0c86188 100755
--- a/py/hwid/v3/hwid_cmdline.py
+++ b/py/hwid/v3/hwid_cmdline.py
@@ -13,7 +13,7 @@
 import sys
 
 import factory_common  # pylint: disable=W0611
-from cros.factory.hwid.v3 import common
+from cros.factory.hwid.v3.bom import ProbedComponentResult
 from cros.factory.hwid.v3 import database
 from cros.factory.hwid.v3 import hwid_utils
 from cros.factory.hwid.v3 import rule
@@ -276,7 +276,7 @@
                                          options.components)
   if options.json_output:
     def _ConvertToDict(obj):
-      if isinstance(obj, (common.ProbedComponentResult, rule.Value)):
+      if isinstance(obj, (ProbedComponentResult, rule.Value)):
         return _ConvertToDict(obj.__dict__)
       if isinstance(obj, list):
         return [_ConvertToDict(item) for item in obj]
@@ -371,7 +371,7 @@
 
 def InitializeDefaultOptions(options):
   if not options.hwid_db_path:
-    options.hwid_db_path = hwid_utils.DEFAULT_HWID_DATA_PATH
+    options.hwid_db_path = hwid_utils.GetDefaultDataPath()
   if options.project is None:
     if sys_utils.InChroot():
       print 'Argument -j/--project is required'
diff --git a/py/hwid/v3/hwid_rule_functions.py b/py/hwid/v3/hwid_rule_functions.py
index ac5b26f..1adcf4c 100644
--- a/py/hwid/v3/hwid_rule_functions.py
+++ b/py/hwid/v3/hwid_rule_functions.py
@@ -18,7 +18,7 @@
 from cros.factory.utils.type_utils import MakeList
 
 
-def GetClassAttributesOnBOM(hwid, comp_cls):
+def GetClassAttributesOnBOM(database, bom, comp_cls):
   """Creates a set of valid rule values to be evaluated with.
 
   This method accepts a HWID context and a component class, and generates a dict
@@ -36,7 +36,8 @@
     }
 
   Args:
-    hwid: The HWID context to extract attributes from.
+    database: The Database to extract attributes from.
+    bom: The BOM object to extract attributes from.
     comp_cls: The component class to retrieve values for.
 
   Returns:
@@ -52,17 +53,17 @@
         if c.component_name:
           results.append(c.component_name)
         continue
-      matched_component = hwid.database.components.MatchComponentsFromValues(
+      matched_component = database.components.MatchComponentsFromValues(
           comp_cls, c.probed_values)
       if matched_component:
         results.extend(matched_component.keys())
     return results
 
-  if comp_cls not in hwid.database.components.GetRequiredComponents():
+  if comp_cls not in database.components.GetRequiredComponents():
     GetLogger().Error('Invalid component class: %r' % comp_cls)
     return None
   # Construct a set of known values from hwid.database and hwid.bom.
-  results = PackProbedValues(hwid.bom, comp_cls)
+  results = PackProbedValues(bom, comp_cls)
   # If the set is empty, add a None element indicating that the component
   # class is missing.
   if not results:
@@ -80,7 +81,7 @@
         all.
   """
   context = GetContext()
-  attrs = GetClassAttributesOnBOM(context.hwid, comp_cls)
+  attrs = GetClassAttributesOnBOM(context.database, context.bom, comp_cls)
   if attrs is None:
     return False
   values = [
@@ -89,7 +90,7 @@
       [any([v.Matches(attr) for attr in attrs]) for v in values])
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['bom', 'database'])
 def ComponentEq(comp_cls, values):
   """Test if the component equals to the values set.
 
@@ -105,7 +106,7 @@
   return _ComponentCompare(comp_cls, values, all)
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['bom', 'database'])
 def ComponentIn(comp_cls, values):
   """Test if the component is in the values set.
 
@@ -121,7 +122,7 @@
   return _ComponentCompare(comp_cls, values, any)
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['bom', 'database'])
 def SetComponent(comp_cls, name):
   """A wrapper method to update {comp_cls: name} pair of BOM and re-generate
   'binary_string' and 'encoded_string' of the HWID context.
@@ -131,11 +132,10 @@
     name: The component name to set to the given component class.
   """
   context = GetContext()
-  context.hwid.bom = context.hwid.database.UpdateComponentsOfBOM(
-      context.hwid.bom, {comp_cls: name})
+  context.database.UpdateComponentsOfBOM(context.bom, {comp_cls: name})
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['bom', 'database'])
 def SetImageId(image_id):
   """A function to set the image id of the given HWID context.
 
@@ -146,34 +146,34 @@
   if isinstance(image_id, str):
     # Convert image_id string to its corresponding encoded value.
     reversed_image_id_dict = dict((value, key) for key, value in
-                                  context.hwid.database.image_id.iteritems())
+                                  context.database.image_id.iteritems())
     if image_id not in reversed_image_id_dict:
       raise HWIDException('Invalid image id: %r' % image_id)
     image_id = reversed_image_id_dict[image_id]
 
-  if image_id not in context.hwid.database.image_id:
+  if image_id not in context.database.image_id:
     raise HWIDException('Invalid image id: %r' % image_id)
-  context.hwid.bom.image_id = image_id
+  context.bom.image_id = image_id
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['bom'])
 def GetImageId():
   """A function to get the image id from the given HWID context.
 
   Returns:
     The image id of the HWID context.
   """
-  return GetContext().hwid.bom.image_id
+  return GetContext().bom.image_id
 
 
-@RuleFunction(['hwid'])
+@RuleFunction(['mode'])
 def GetOperationMode():
   """A function to get the set of operation modes of the HWID context.
 
   Returns:
     The set of operations modes currently enabled on the given HWID context.
   """
-  return GetContext().hwid.mode
+  return GetContext().mode
 
 
 @RuleFunction(['device_info'])
@@ -237,7 +237,7 @@
 
 
 # pylint: disable=W0622
-@RuleFunction(['hwid'])
+@RuleFunction(['database'])
 def CheckRegistrationCode(code, type=None, device=None):
   """A wrapper method to verify registration code.
 
@@ -267,7 +267,7 @@
   # (e.g., "spring", not "daisy_spring"). For Zerg devices this may be chassis
   # or project ID.
   if device is None:
-    device = GetContext().hwid.database.project.lower()
+    device = GetContext().database.project.lower()
   registration_codes.CheckRegistrationCode(code, type=type, device=device)
 
 
diff --git a/py/hwid/v3/hwid_rule_functions_unittest.py b/py/hwid/v3/hwid_rule_functions_unittest.py
index 21eb38a..48b723a 100755
--- a/py/hwid/v3/hwid_rule_functions_unittest.py
+++ b/py/hwid/v3/hwid_rule_functions_unittest.py
@@ -15,7 +15,7 @@
 import cros.factory.hwid.v3.common_rule_functions  # pylint: disable=W0611
 from cros.factory.hwid.v3.common import HWIDException
 from cros.factory.hwid.v3.database import Database
-from cros.factory.hwid.v3.encoder import Encode
+from cros.factory.hwid.v3.transformer import Encode
 from cros.factory.hwid.v3 import hwid_utils
 from cros.factory.hwid.v3.hwid_rule_functions import CheckRegistrationCode
 from cros.factory.hwid.v3.hwid_rule_functions import ComponentEq
@@ -49,7 +49,7 @@
         os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
     bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
                                                   self.results[0])
-    bom = self.database.UpdateComponentsOfBOM(bom, {
+    self.database.UpdateComponentsOfBOM(bom, {
         'keyboard': 'keyboard_us', 'dram': 'dram_0',
         'display_panel': 'display_panel_0'})
     self.hwid = Encode(self.database, bom)
@@ -66,8 +66,9 @@
             'registration_code': 'buz'
         }
     }
-    self.context = Context(hwid=self.hwid, device_info=self.device_info,
-                           vpd=self.vpd)
+    self.context = Context(
+        database=self.hwid.database, bom=self.hwid.bom, mode=self.hwid.mode,
+        device_info=self.device_info, vpd=self.vpd)
     SetContext(self.context)
 
   def testRule(self):
@@ -171,10 +172,12 @@
         rule.Evaluate, self.context)
 
   def testGetClassAttributesOnBOM(self):
-    cpu_attrs = GetClassAttributesOnBOM(self.hwid, 'cpu')
+    cpu_attrs = GetClassAttributesOnBOM(
+        self.hwid.database, self.hwid.bom, 'cpu')
     self.assertEquals(['cpu_5'], cpu_attrs)
 
-    self.assertEquals(None, GetClassAttributesOnBOM(self.hwid, 'foo'))
+    self.assertEquals(
+        None, GetClassAttributesOnBOM(self.hwid.database, self.hwid.bom, 'foo'))
     self.assertEquals("ERROR: Invalid component class: 'foo'",
                       GetLogger().error[0].message)
 
@@ -191,17 +194,17 @@
   def testSetComponent(self):
     SetComponent('cpu', 'cpu_3')
     self.assertEquals(
-        'cpu_3', self.context.hwid.bom.components['cpu'][0].component_name)
+        'cpu_3', self.context.bom.components['cpu'][0].component_name)
     self.assertEquals(
-        3, self.context.hwid.bom.encoded_fields['cpu'])
+        3, self.context.bom.encoded_fields['cpu'])
     self.assertEquals('0000000000111010010001', self.hwid.binary_string)
     self.assertEquals('CHROMEBOOK AA5E-IVL', self.hwid.encoded_string)
     SetComponent('cellular', 'cellular_0')
     self.assertEquals(
         'cellular_0',
-        self.context.hwid.bom.components['cellular'][0].component_name)
+        self.context.bom.components['cellular'][0].component_name)
     self.assertEquals(
-        1, self.context.hwid.bom.encoded_fields['cellular'])
+        1, self.context.bom.encoded_fields['cellular'])
     self.assertEquals('0000000000111110010001', self.hwid.binary_string)
     self.assertEquals('CHROMEBOOK AA7E-IWF', self.hwid.encoded_string)
 
diff --git a/py/hwid/v3/hwid_utils.py b/py/hwid/v3/hwid_utils.py
index cddaec0..fc875ca 100644
--- a/py/hwid/v3/hwid_utils.py
+++ b/py/hwid/v3/hwid_utils.py
@@ -7,21 +7,17 @@
 import collections
 import logging
 import os
-import subprocess
 
 import factory_common  # pylint: disable=W0611
 from cros.factory.hwid.v3.bom import BOM
+from cros.factory.hwid.v3.bom import ProbedComponentResult
 from cros.factory.hwid.v3 import builder
 from cros.factory.hwid.v3 import common
 from cros.factory.hwid.v3.database import Database
-from cros.factory.hwid.v3 import decoder
-from cros.factory.hwid.v3 import encoder
 from cros.factory.hwid.v3 import rule
+from cros.factory.hwid.v3 import transformer
 from cros.factory.hwid.v3 import yaml_wrapper as yaml
-from cros.factory.utils import cros_board_utils
-from cros.factory.utils import process_utils
 from cros.factory.utils import json_utils
-from cros.factory.utils import sys_utils
 from cros.factory.utils import type_utils
 
 
@@ -77,10 +73,11 @@
   """
   hwid_mode = _HWIDMode(rma_mode)
   # Construct a base BOM from probe_results.
-  hwid = encoder.Encode(db, bom, mode=hwid_mode, skip_check=True)
+  hwid = transformer.Encode(db, bom, mode=hwid_mode, skip_check=True)
 
   # Update unprobeable components with rules defined in db before verification.
-  context_args = dict(hwid=hwid, device_info=device_info)
+  context_args = dict(database=hwid.database, bom=hwid.bom, mode=hwid.mode,
+                      device_info=device_info)
   if vpd is not None:
     context_args['vpd'] = vpd
   context = rule.Context(**context_args)
@@ -99,7 +96,7 @@
   Returns:
     The decoded HWIDv3 context object.
   """
-  return decoder.Decode(db, encoded_string)
+  return transformer.Decode(db, encoded_string)
 
 
 def ParseDecodedHWID(hwid):
@@ -164,11 +161,11 @@
     HWIDException if verification fails.
   """
   hwid_mode = _HWIDMode(rma_mode)
-  hwid = decoder.Decode(db, encoded_string, mode=hwid_mode)
+  hwid = transformer.Decode(db, encoded_string, mode=hwid_mode)
   hwid.VerifyBOM(bom)
   hwid.VerifyComponentStatus(current_phase=current_phase)
   hwid.VerifyPhase(current_phase)
-  context_args = dict(hwid=hwid)
+  context_args = dict(database=hwid.database, bom=hwid.bom, mode=hwid.mode)
   if vpd is not None:
     context_args['vpd'] = vpd
   context = rule.Context(**context_args)
@@ -258,7 +255,7 @@
       for comp_cls, attr_list in attr_dict.iteritems():
         if attr_list is None:
           comp_items.append('None')
-          components[comp_cls].append(common.ProbedComponentResult(
+          components[comp_cls].append(ProbedComponentResult(
               None, None, common.MISSING_COMPONENT_ERROR(comp_cls)))
         else:
           for attrs in attr_list:
@@ -278,14 +275,15 @@
                             attrs['status'])
               break
             comp_items.append(attrs['name'])
-            components[comp_cls].append(common.ProbedComponentResult(
+            components[comp_cls].append(ProbedComponentResult(
                 attrs['name'], attrs['values'], None))
       component_list.append(' '.join(comp_items))
     if pass_check:
       bom = BOM(db.project, encoding_pattern, image_id, components,
                 encoded_fields)
-      binary_string = encoder.BOMToBinaryString(db, bom)
-      encoded_string = encoder.BinaryStringToEncodedString(db, binary_string)
+      binary_string = transformer.BOMToBinaryString(db, bom)
+      encoded_string = transformer.BinaryStringToEncodedString(
+          db, binary_string)
       hwid_dict[encoded_string] = ','.join(component_list)
 
   def _RecursivelyGenerate(index=None, encoded_fields=None):
@@ -371,6 +369,8 @@
   elif raw_data:
     return json_utils.LoadStr(raw_data)
   else:
+    from cros.factory.utils import process_utils
+    from cros.factory.utils import sys_utils
     if sys_utils.InChroot():
       raise ValueError('Cannot probe components in chroot. Please specify '
                        'probed results with an input file. If you are running '
@@ -424,7 +424,7 @@
       comp_status = database.components.GetComponentStatus(comp_cls, comp_name)
       if comp_status != common.HWID.COMPONENT_STATUS.unsupported:
         probed_components[comp_cls].append(
-            common.ProbedComponentResult(comp_name, None, None))
+            ProbedComponentResult(comp_name, None, None))
         return True
     return False
 
@@ -439,7 +439,7 @@
       # Probeable comp_cls but no component is found in probe results.
       if comp_cls in database.components.probeable:
         probed_components[comp_cls].append(
-            common.ProbedComponentResult(
+            ProbedComponentResult(
                 None, None, common.MISSING_COMPONENT_ERROR(comp_cls)))
       else:
         # Unprobeable comp_cls and only has 1 component, treat as found.
@@ -450,16 +450,15 @@
               comp_cls, comp_name)
           if comp_status == common.HWID.COMPONENT_STATUS.supported:
             probed_components[comp_cls].append(
-                common.ProbedComponentResult(comp_name, None, None))
+                ProbedComponentResult(comp_name, None, None))
       continue
 
     for probed_value in probed_comp_values:
       # Unprobeable comp_cls but component is found in probe results.
       if comp_cls not in database.components.probeable:
-        probed_components[comp_cls].append(
-            common.ProbedComponentResult(
-                None, probed_value,
-                common.UNPROBEABLE_COMPONENT_ERROR(comp_cls)))
+        probed_components[comp_cls].append(ProbedComponentResult(
+            None, probed_value,
+            common.UNPROBEABLE_COMPONENT_ERROR(comp_cls)))
         continue
 
       matched_comps = database.components.MatchComponentsFromValues(
@@ -467,7 +466,7 @@
       if matched_comps is None:
         # If there is no default item, add invalid error.
         if not TryAddDefaultItem(probed_components, comp_cls):
-          probed_components[comp_cls].append(common.ProbedComponentResult(
+          probed_components[comp_cls].append(ProbedComponentResult(
               None, probed_value,
               common.INVALID_COMPONENT_ERROR(comp_cls, probed_value)))
       elif len(matched_comps) == 1:
@@ -476,16 +475,14 @@
             comp_cls, comp_name)
         if comp_status == common.HWID.COMPONENT_STATUS.supported:
           probed_components[comp_cls].append(
-              common.ProbedComponentResult(
-                  comp_name, comp_data['values'], None))
+              ProbedComponentResult(comp_name, comp_data['values'], None))
         else:
-          probed_components[comp_cls].append(
-              common.ProbedComponentResult(
-                  comp_name, comp_data['values'],
-                  common.UNSUPPORTED_COMPONENT_ERROR(comp_cls, comp_name,
-                                                     comp_status)))
+          probed_components[comp_cls].append(ProbedComponentResult(
+              comp_name, comp_data['values'],
+              common.UNSUPPORTED_COMPONENT_ERROR(comp_cls, comp_name,
+                                                 comp_status)))
       elif len(matched_comps) > 1:
-        probed_components[comp_cls].append(common.ProbedComponentResult(
+        probed_components[comp_cls].append(ProbedComponentResult(
             None, probed_value,
             common.AMBIGUOUS_COMPONENT_ERROR(
                 comp_cls, probed_value, matched_comps)))
@@ -531,6 +528,7 @@
   """
   assert not (run_vpd and vpd_data_file)
   if run_vpd:
+    from cros.factory.utils import sys_utils
     vpd_tool = sys_utils.VPDTool()
     return {
         'ro': vpd_tool.GetAllData(partition=vpd_tool.RO_PARTITION),
@@ -550,7 +548,7 @@
 def ProbeProject():
   """Probes the project name.
 
-  This function will try to run the command `mosys platform chassis` to get the
+  This function will try to run the command `mosys platform model` to get the
   project name.  If failed, this function will return the board name as legacy
   chromebook projects used to assume that the board name is equal to the
   project name.
@@ -558,6 +556,11 @@
   Returns:
     The probed project name as a string.
   """
+  import subprocess
+
+  from cros.factory.utils import process_utils
+  from cros.factory.utils import cros_board_utils
+
   try:
     project = process_utils.CheckOutput(
         ['mosys', 'platform', 'model']).strip().lower()
@@ -570,13 +573,23 @@
   return cros_board_utils.BuildBoard().short_name
 
 
-# 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 sys_utils.InChroot()
-    else '/usr/local/factory/hwid')
+_DEFAULT_DATA_PATH = None
+
+def GetDefaultDataPath():
+  """Returns the expected location of HWID data within a factory image or the
+  chroot.
+  """
+  from cros.factory.utils import sys_utils
+
+  global _DEFAULT_DATA_PATH  # pylint: disable=global-statement
+  if _DEFAULT_DATA_PATH is None:
+    if sys_utils.InChroot():
+      _DEFAULT_DATA_PATH = os.path.join(
+          os.environ['CROS_WORKON_SRCROOT'],
+          'src', 'platform', 'chromeos-hwid', 'v3')
+    else:
+      _DEFAULT_DATA_PATH = '/usr/local/factory/hwid'
+  return _DEFAULT_DATA_PATH
 
 
 def GetHWIDBundleName(project=None):
diff --git a/py/hwid/v3/transformer.py b/py/hwid/v3/transformer.py
new file mode 100644
index 0000000..b998969
--- /dev/null
+++ b/py/hwid/v3/transformer.py
@@ -0,0 +1,369 @@
+# Copyright 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.
+
+"""Implementation of HWID v3 encoder and decoder."""
+
+import collections
+import pprint
+
+import factory_common  # pylint: disable=W0611
+
+from cros.factory.hwid.v3.bom import BOM
+from cros.factory.hwid.v3.bom import ProbedComponentResult
+from cros.factory.hwid.v3 import common
+from cros.factory.hwid.v3.base32 import Base32
+from cros.factory.hwid.v3.base8192 import Base8192
+from cros.factory.hwid.v3.identity import Identity
+from cros.factory.utils import type_utils
+
+
+_Encoder = {
+    common.HWID.ENCODING_SCHEME.base32: Base32,
+    common.HWID.ENCODING_SCHEME.base8192: Base8192
+}
+
+_Decoder = {
+    common.HWID.ENCODING_SCHEME.base32: Base32,
+    common.HWID.ENCODING_SCHEME.base8192: Base8192
+}
+
+
+def VerifyBinaryString(database, binary_string):
+  """Verifies the binary string.
+
+  Args:
+    binary_string: The binary string to verify.
+
+  Raises:
+    HWIDException if verification fails.
+  """
+  if set(binary_string) - set('01'):
+    raise common.HWIDException('Invalid binary string: %r' % binary_string)
+
+  if '1' not in binary_string:
+    raise common.HWIDException('Binary string %r does not have stop bit set',
+                               binary_string)
+  # Truncate trailing 0s.
+  string_without_paddings = binary_string[:binary_string.rfind('1') + 1]
+
+  image_id = database.pattern.GetImageIdFromBinaryString(binary_string)
+  total_bit_length = database.pattern.GetTotalBitLength(image_id)
+  if len(string_without_paddings) > total_bit_length:
+    raise common.HWIDException(
+        'Invalid bit string length of %r. Expected length <= %d, got length %d'
+        % (binary_string, total_bit_length, len(string_without_paddings)))
+
+
+def VerifyEncodedStringFormat(encoded_string):
+  """Verifies that the format of the given encoded string.
+
+  Checks that the string matches either base32 or base8192 format.
+
+  Args:
+    encoded_string: The encoded string to verify.
+
+  Raises:
+    HWIDException if verification fails.
+  """
+  if not any(hwid_format.match(encoded_string) for hwid_format in
+             common.HWID_FORMAT.itervalues()):
+    raise common.HWIDException(
+        'HWID string %r is neither base32 nor base8192 encoded' %
+        encoded_string)
+
+
+def VerifyEncodedString(database, encoded_string):
+  """Verifies the given encoded string.
+
+  Args:
+    encoded_string: The encoded string to verify.
+
+  Raises:
+    HWIDException if verification fails.
+  """
+  try:
+    image_id = database.pattern.GetImageIdFromEncodedString(encoded_string)
+    encoding_scheme = database.pattern.GetPatternByImageId(
+        image_id)['encoding_scheme']
+    project, bom_checksum = common.HWID_FORMAT[encoding_scheme].findall(
+        encoded_string)[0]
+  except IndexError:
+    raise common.HWIDException(
+        'Invalid HWID string format: %r' % encoded_string)
+  if len(bom_checksum) < 2:
+    raise common.HWIDException(
+        'Length of encoded string %r is less than 2 characters' %
+        bom_checksum)
+  if project != database.project.upper():
+    raise common.HWIDException('Invalid project name: %r' % project)
+  # Verify the checksum
+  stripped = encoded_string.replace('-', '')
+  hwid = stripped[:-2]
+  checksum = stripped[-2:]
+  if encoding_scheme == common.HWID.ENCODING_SCHEME.base32:
+    expected_checksum = Base32.Checksum(hwid)
+  elif encoding_scheme == common.HWID.ENCODING_SCHEME.base8192:
+    expected_checksum = Base8192.Checksum(hwid)
+  if checksum != expected_checksum:
+    raise common.HWIDException('Checksum of %r mismatch (expected %r)' % (
+        encoded_string, expected_checksum))
+
+def VerifyBOM(database, bom, probeable_only=False):
+  """Verifies the data contained in the given BOM object matches the settings
+  and definitions in the database.
+
+  Because the components for each image ID might be different, for example a
+  component might be removed in later build. We only verify the components in
+  the target image ID, not all components listed in the database.
+
+  When the BOM is decoded by HWID string, it would contain the information of
+  every component recorded in the pattern. But if the BOM object is created by
+  the probed result, it does not contain the unprobeable component before
+  evaluating the rule. We should verify the probeable components only.
+
+  Args:
+    bom: The BOM object to verify.
+    probeable_only: True to verify the probeable component only.
+
+  Raises:
+    HWIDException if verification fails.
+  """
+  if bom.project != database.project:
+    raise common.HWIDException('Invalid project name. Expected %r, got %r' %
+                               (database.project, bom.project))
+
+  if bom.encoding_pattern_index not in database.encoding_patterns:
+    raise common.HWIDException('Invalid encoding pattern: %r' %
+                               bom.encoding_pattern_index)
+  if bom.image_id not in database.image_id:
+    raise common.HWIDException('Invalid image id: %r' % bom.image_id)
+
+  # All the classes encoded in the pattern should exist in BOM.
+  # Ignore unprobeable components if probeable_only is True.
+  missing_comp = []
+  expected_encoded_fields = database.pattern.GetFieldNames(bom.image_id)
+  for comp_cls in database.GetActiveComponents(bom.image_id):
+    if (comp_cls not in bom.components and
+        (comp_cls in database.components.probeable or not probeable_only)):
+      missing_comp.append(comp_cls)
+  if missing_comp:
+    raise common.HWIDException('Missing component classes: %r',
+                               ', '.join(sorted(missing_comp)))
+
+  bom_encoded_fields = type_utils.MakeSet(bom.encoded_fields.keys())
+  db_encoded_fields = type_utils.MakeSet(expected_encoded_fields)
+  # Every encoded field defined in the database must present in BOM.
+  if db_encoded_fields - bom_encoded_fields:
+    raise common.HWIDException('Missing encoded fields in BOM: %r',
+                               ', '.join(sorted(db_encoded_fields -
+                                                bom_encoded_fields)))
+
+  # All the probeable component values in the BOM should exist in the
+  # database.
+  unknown_values = []
+  for comp_cls, probed_values in bom.components.iteritems():
+    if comp_cls not in database.components.probeable:
+      continue
+    for element in probed_values:
+      probed_values = element.probed_values
+      if probed_values is None:
+        continue
+      found_comps = database.components.MatchComponentsFromValues(
+          comp_cls, probed_values, include_default=True)
+      if not found_comps:
+        unknown_values.append('%s:%s' % (comp_cls, pprint.pformat(
+            probed_values, indent=0, width=1024)))
+  if unknown_values:
+    raise common.HWIDException('Unknown component values: %r' %
+                               ', '.join(sorted(unknown_values)))
+
+  # All the encoded index should exist in the database.
+  invalid_fields = []
+  for field_name in expected_encoded_fields:
+    # Ignore the field containing unprobeable component.
+    if probeable_only and not all(
+        [comp_cls in database.components.probeable
+         for comp_cls in database.encoded_fields[field_name][0].keys()]):
+      continue
+    index = bom.encoded_fields[field_name]
+    if index is None or index not in database.encoded_fields[field_name]:
+      invalid_fields.append(field_name)
+
+  if invalid_fields:
+    raise common.HWIDException('Encoded fields %r have unknown indices' %
+                               ', '.join(sorted(invalid_fields)))
+
+
+def BOMToBinaryString(database, bom):
+  """Encodes the given BOM object to a binary string.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for encoding.
+
+  Returns:
+    A binary string.
+  """
+  VerifyBOM(database, bom)
+  bit_length = database.pattern.GetTotalBitLength(bom.image_id)
+  binary_list = bit_length * [0]
+
+  # Fill in header.
+  binary_list[0] = bom.encoding_pattern_index
+  for i in xrange(1, 5):
+    binary_list[i] = (bom.image_id >> (4 - i)) & 1
+  # Fill in each bit.
+  bit_mapping = database.pattern.GetBitMapping(bom.image_id)
+  for index, (field, bit_offset) in bit_mapping.iteritems():
+    binary_list[index] = (bom.encoded_fields[field] >> bit_offset) & 1
+  # Set stop bit.
+  binary_list[bit_length - 1] = 1
+  return ''.join(['%d' % bit for bit in binary_list])
+
+
+def BinaryStringToEncodedString(database, binary_string):
+  """Encodes the given binary string to a encoded string.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for encoding.
+    binary_string: A string of '0's and '1's.
+
+  Returns:
+    An encoded string with project name, base32-encoded HWID, and checksum.
+  """
+  VerifyBinaryString(database, binary_string)
+  image_id = database.pattern.GetImageIdFromBinaryString(binary_string)
+  encoding_scheme = database.pattern.GetPatternByImageId(
+      image_id)['encoding_scheme']
+  encoder = _Encoder[encoding_scheme]
+  encoded_string = encoder.Encode(binary_string)
+  # Make project name part of the checksum.
+  encoded_string += encoder.Checksum(
+      database.project.upper() + ' ' + encoded_string)
+  # Insert dashes to increase readibility.
+  encoded_string = ('-'.join(
+      [encoded_string[i:i + encoder.DASH_INSERTION_WIDTH]
+       for i in xrange(0, len(encoded_string), encoder.DASH_INSERTION_WIDTH)]))
+  return database.project.upper() + ' ' + encoded_string
+
+
+def Encode(database, bom, mode=common.HWID.OPERATION_MODE.normal,
+           skip_check=False):
+  """Encodes all the given BOM object.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for encoding.
+    bom: A BOM object.
+    mode: The operation mode of the generated HWID object. Valid values are:
+        ('normal', 'rma')
+    skip_check: Whether to skip HWID verification checks. Set to True when
+        generating HWID skeleton objects for further processing.
+
+  Returns:
+    A HWID object which contains the BOM, the binary string, and the encoded
+    string derived from the given BOM object.
+  """
+  hwid = common.HWID(database, bom=bom, mode=mode, skip_check=skip_check)
+  return hwid
+
+
+def BinaryStringToBOM(database, binary_string):
+  """Decodes the given binary string to a BOM object.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for decoding.
+    binary_string: A binary string.
+
+  Returns:
+    A BOM object
+  """
+  VerifyBinaryString(database, binary_string)
+  stripped_binary_string = binary_string[:binary_string.rfind('1')]
+
+  project = database.project
+  encoding_pattern = int(stripped_binary_string[0], 2)
+  image_id = database.pattern.GetImageIdFromBinaryString(binary_string)
+
+  # Construct the encoded fields dict.
+  encoded_fields = collections.defaultdict(int)
+  for field_name in database.pattern.GetFieldNames(image_id):
+    encoded_fields[field_name] = 0
+  bit_mapping = database.pattern.GetBitMapping(
+      image_id=image_id, binary_string_length=len(stripped_binary_string))
+  for i, (field, bit_offset) in bit_mapping.iteritems():
+    if i >= len(stripped_binary_string):
+      break
+    encoded_fields[field] += int(stripped_binary_string[i], 2) << bit_offset
+
+  # Check that all the encoded field indices are valid.
+  expected_encoded_fields = database.pattern.GetFieldNames(image_id)
+  missing_fields = set(expected_encoded_fields) - set(encoded_fields.keys())
+  if missing_fields:
+    raise common.HWIDException('Index of the fields are missing: %r' %
+                               list(missing_fields))
+  for field in encoded_fields:
+    if encoded_fields[field] not in database.encoded_fields[field]:
+      raise common.HWIDException('Invalid encoded field index: {%r: %r}' %
+                                 (field, encoded_fields[field]))
+
+  # Construct the components dict.
+  components = collections.defaultdict(list)
+  for field, index in encoded_fields.iteritems():
+    # pylint: disable=W0212
+    attr_dict = database._GetAttributesByIndex(field, index)
+    for comp_cls, attr_list in attr_dict.iteritems():
+      if attr_list is None:
+        components[comp_cls].append(ProbedComponentResult(
+            None, None, common.MISSING_COMPONENT_ERROR(comp_cls)))
+      else:
+        for attrs in attr_list:
+          components[comp_cls].append(ProbedComponentResult(
+              attrs['name'], attrs['values'], None))
+
+  return BOM(project, encoding_pattern, image_id, components, encoded_fields)
+
+
+def EncodedStringToBinaryString(database, encoded_string):
+  """Decodes the given encoded HWID string to a binary string.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for decoding.
+    encoded_string: An encoded string (with or without dashed).
+
+  Returns:
+    A binary string.
+  """
+  VerifyEncodedStringFormat(encoded_string)
+  image_id = database.pattern.GetImageIdFromEncodedString(encoded_string)
+  encoding_scheme = database.pattern.GetPatternByImageId(
+      image_id)['encoding_scheme']
+  VerifyEncodedString(database, encoded_string)
+  _, hwid_string = encoded_string.split(' ')
+  hwid_string = hwid_string.replace('-', '')
+  return _Decoder[encoding_scheme].Decode(
+      hwid_string)[:-_Decoder[encoding_scheme].CHECKSUM_SIZE].rstrip('0')
+
+
+def Decode(database, encoded_string, mode=common.HWID.OPERATION_MODE.normal):
+  """Decodes the given encoded string to a HWID object.
+
+  Args:
+    database: A Database object that is used to provide device-specific
+        information for decoding.
+    encoded_string: An encoded string.
+    mode: The operation mode of the generated HWID object. Valid values are:
+        ('normal', 'rma')
+
+  Returns:
+    A HWID object which contains the BOM, the binary string, and the encoded
+    string derived from the given encoded string.
+  """
+  binary_string = EncodedStringToBinaryString(database, encoded_string)
+  identity = Identity(database.project, binary_string, encoded_string)
+  bom = BinaryStringToBOM(database, binary_string)
+  return common.HWID(database, bom=bom, identity=identity, mode=mode)
diff --git a/py/hwid/v3/transformer_unittest.py b/py/hwid/v3/transformer_unittest.py
new file mode 100755
index 0000000..fe1b5e4
--- /dev/null
+++ b/py/hwid/v3/transformer_unittest.py
@@ -0,0 +1,419 @@
+#!/usr/bin/env python
+# Copyright 2017 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.
+
+import copy
+import os
+import unittest
+
+import factory_common  # pylint: disable=W0611
+
+from cros.factory.hwid.v3.bom import ProbedComponentResult
+from cros.factory.hwid.v3.common import HWIDException
+from cros.factory.hwid.v3.database import Database
+from cros.factory.hwid.v3.transformer import BinaryStringToBOM
+from cros.factory.hwid.v3.transformer import BinaryStringToEncodedString
+from cros.factory.hwid.v3.transformer import BOMToBinaryString
+from cros.factory.hwid.v3.transformer import Decode
+from cros.factory.hwid.v3.transformer import Encode
+from cros.factory.hwid.v3.transformer import EncodedStringToBinaryString
+from cros.factory.hwid.v3.transformer import VerifyBinaryString
+from cros.factory.hwid.v3.transformer import VerifyEncodedString
+from cros.factory.hwid.v3.transformer import VerifyBOM
+from cros.factory.hwid.v3 import hwid_utils
+from cros.factory.hwid.v3.rule import Value
+from cros.factory.utils import json_utils
+
+
+_TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'testdata')
+
+
+class VerifyBinaryStringTest(unittest.TestCase):
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_db.yaml'))
+
+  def testVerifyBinaryString(self):
+    func = lambda s: VerifyBinaryString(self.database, s)
+
+    self.assertEquals(None, func('0000000000111010000011000'))
+    self.assertRaisesRegexp(
+        HWIDException, r'Invalid binary string: .*',
+        func, '020001010011011011000')
+    self.assertRaisesRegexp(
+        HWIDException, r'Binary string .* does not have stop bit set',
+        func, '00000')
+    self.assertRaisesRegexp(
+        HWIDException, r'Invalid bit string length',
+        func, '000000000010100110110111000')
+
+
+class VerifyEncodedStringTest(unittest.TestCase):
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_db.yaml'))
+
+  def testVerifyEncodedString(self):
+    func = lambda s: VerifyEncodedString(self.database, s)
+
+    self.assertEquals(None, func('CHROMEBOOK AW3L-M7I7-V'))
+    self.assertRaisesRegexp(
+        HWIDException, r'Invalid HWID string format', func, 'AW3L-M7I5-4')
+    self.assertRaisesRegexp(
+        HWIDException, r'Length of encoded string .* is less than 2 characters',
+        func, 'FOO A')
+    self.assertRaisesRegexp(
+        HWIDException, r'Invalid project name', func, 'FOO AW3L-M7IK-W')
+    self.assertRaisesRegexp(
+        HWIDException, r'Checksum of .* mismatch',
+        func, 'CHROMEBOOK AW3L-M7IA-B')
+
+
+class VerifyBOMTest(unittest.TestCase):
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_db.yaml'))
+    self.results = json_utils.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
+    self.boms = [hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                         probed_results)
+                 for probed_results in self.results]
+
+  def testVerifyBOM(self):
+    # Before evaluating rule to update the unprobeable component, we only verify
+    # the probeable components.
+    bom = self.boms[0]
+    self.assertEquals(None, VerifyBOM(self.database, bom, True))
+
+    original_value = bom.project
+    bom.project = 'FOO'
+    with self.assertRaisesRegexp(HWIDException,
+                                 r'Invalid project name. Expected .*, got .*'):
+      VerifyBOM(self.database, bom, True)
+    bom.project = original_value
+
+    original_value = bom.encoding_pattern_index
+    bom.encoding_pattern_index = 2
+    with self.assertRaisesRegexp(HWIDException, r'Invalid encoding pattern'):
+      VerifyBOM(self.database, bom, True)
+    bom.encoding_pattern_index = original_value
+
+    original_value = bom.image_id
+    bom.image_id = 6
+    with self.assertRaisesRegexp(HWIDException, r'Invalid image id: .*'):
+      VerifyBOM(self.database, bom, True)
+    bom.image_id = original_value
+
+    original_value = bom.encoded_fields['cpu']
+    bom.encoded_fields['cpu'] = 8
+    with self.assertRaisesRegexp(HWIDException,
+                                 r'Encoded fields .* have unknown indices'):
+      VerifyBOM(self.database, bom, True)
+    bom.encoded_fields['cpu'] = original_value
+
+    original_value = bom.components['cpu']
+    bom.components['cpu'] = [ProbedComponentResult(
+        'cpu', {'name': Value('foo'), 'cores': Value('4')}, None)]
+    with self.assertRaisesRegexp(HWIDException, r'Unknown component values:.*'):
+      VerifyBOM(self.database, bom, True)
+    bom.components['cpu'] = original_value
+
+    original_value = bom.encoded_fields['cpu']
+    bom.encoded_fields.pop('cpu')
+    with self.assertRaisesRegexp(HWIDException,
+                                 r'Missing encoded fields in BOM: .*'):
+      VerifyBOM(self.database, bom, True)
+    bom.encoded_fields['cpu'] = original_value
+
+
+class NewVerifyBOMTest(unittest.TestCase):
+  """Test the new style of HWID database.
+
+  - Split key_root and key_recovery to a new component: firmware_keys
+  - Separate firmware field to let each field only has one component
+  - Add default argument in audio_codec and cellular
+  """
+
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_new_db.yaml'))
+    self.results = json_utils.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
+    self.boms = [hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                         probed_results)
+                 for probed_results in self.results]
+
+  def testVerifyBOMWithDefault(self):
+    self.assertEquals(None, VerifyBOM(self.database, self.boms[6], True))
+
+
+class DecoderTest(unittest.TestCase):
+
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_db.yaml'))
+    self.results = json_utils.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
+    self.expected_components_from_db = {
+        'audio_codec': [('codec_1', {'compact_str': Value('Codec 1')}, None),
+                        ('hdmi_1', {'compact_str': Value('HDMI 1')}, None)],
+        'battery': [('battery_huge',
+                     {'tech': Value('Battery Li-ion'),
+                      'size': Value('10000000')},
+                     None)],
+        'bluetooth': [('bluetooth_0',
+                       {'idVendor': Value('0123'), 'idProduct': Value('abcd'),
+                        'bcd': Value('0001')},
+                       None)],
+        'cellular': [(None, None, "Missing 'cellular' component")],
+        'cpu': [('cpu_5',
+                 {'name': Value('CPU @ 2.80GHz'), 'cores': Value('4')},
+                 None)],
+        'display_panel': [('display_panel_0', None, None)],
+        'dram': [('dram_0',
+                  {'vendor': Value('DRAM 0'), 'size': Value('4G')},
+                  None)],
+        'ec_flash_chip': [('ec_flash_chip_0',
+                           {'compact_str': Value('EC Flash Chip')},
+                           None)],
+        'embedded_controller': [('embedded_controller_0',
+                                 {'compact_str': Value('Embedded Controller')},
+                                 None)],
+        'flash_chip': [('flash_chip_0',
+                        {'compact_str': Value('Flash Chip')},
+                        None)],
+        'hash_gbb': [('hash_gbb_0',
+                      {'compact_str': Value('gv2#hash_gbb_0')},
+                      None)],
+        'key_recovery': [('key_recovery_0',
+                          {'compact_str': Value('kv3#key_recovery_0')},
+                          None)],
+        'key_root': [('key_root_0',
+                      {'compact_str': Value('kv3#key_root_0')},
+                      None)],
+        'keyboard': [('keyboard_us', None, None)],
+        'ro_ec_firmware': [('ro_ec_firmware_0',
+                            {'compact_str': Value('ev2#ro_ec_firmware_0')},
+                            None)],
+        'ro_main_firmware': [('ro_main_firmware_0',
+                              {'compact_str': Value('mv2#ro_main_firmware_0')},
+                              None)],
+        'storage': [('storage_0',
+                     {'type': Value('SSD'), 'size': Value('16G'),
+                      'serial': Value(r'^#123\d+$', is_re=True)},
+                     None)],
+        'video': [('camera_0',
+                   {'idVendor': Value('4567'), 'idProduct': Value('abcd'),
+                    'type': Value('webcam')},
+                   None)]}
+
+  def _CheckBOM(self, reference_bom, bom):
+    """Check the BOM decoded from HWID is equivalent to the reference BOM.
+
+    reference_bom (generated from probed result) has all information of
+    encoded_fields and component, but bom (generated from binary string) only
+    has the information of the pattern. Therefore we check bom is the subset
+    of the reference_bom.
+    """
+    self.assertEquals(reference_bom.project, bom.project)
+    self.assertEquals(reference_bom.encoding_pattern_index,
+                      bom.encoding_pattern_index)
+    self.assertEquals(reference_bom.image_id, bom.image_id)
+    for comp_cls in bom.encoded_fields:
+      self.assertEquals(reference_bom.encoded_fields[comp_cls],
+                        bom.encoded_fields[comp_cls])
+    for comp_cls in bom.components:
+      self.assertEquals(self.expected_components_from_db[comp_cls],
+                        bom.components[comp_cls])
+
+  def testEncodedStringToBinaryString(self):
+    self.assertEquals('0000000000111010000011',
+                      EncodedStringToBinaryString(
+                          self.database, 'CHROMEBOOK AA5A-Y6L'))
+    self.assertEquals('0001000000111010000011',
+                      EncodedStringToBinaryString(
+                          self.database, 'CHROMEBOOK C2H-I3Q-A6Q'))
+    self.assertEquals('1000000000111010000011',
+                      EncodedStringToBinaryString(
+                          self.database, 'CHROMEBOOK QA5A-YCJ'))
+
+  def testBinaryStringToBOM(self):
+    reference_bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                            self.results[0])
+    self.database.UpdateComponentsOfBOM(reference_bom, {
+        'keyboard': 'keyboard_us',
+        'display_panel': 'display_panel_0'})
+    bom = BinaryStringToBOM(self.database, '0000000000111010000011')
+    self._CheckBOM(reference_bom, bom)
+
+    bom = BinaryStringToBOM(self.database, '0000000001111010000011')
+    self.assertEquals(1, bom.encoded_fields['firmware'])
+    self.assertEquals(2, BinaryStringToBOM(
+        self.database, '0001000000111010000011').image_id)
+    self.assertEquals(1, BinaryStringToBOM(
+        self.database, '1000000000111010000011').encoding_pattern_index)
+    self.assertRaisesRegexp(
+        HWIDException, r"Invalid encoded field index: {'cpu': 6}",
+        BinaryStringToBOM, self.database, '0000000000111000010011')
+
+  def testIncompleteBinaryStringToBOM(self):
+    # The latest pattern in the test database has 16 bits (plus the image ID and
+    # the stop bit), with the last three bits being one two-bit storage_field,
+    # and one one-bit cpu_field.
+
+    # Test with 21 bits here. This should be regarded as a valid binary string
+    # that was generated before we extended cpu_field.
+    bom = BinaryStringToBOM(
+        self.database,
+        '00000'         # image ID
+        '0000111101000'  # 13 bits, up through second cpu_field
+        '00'            # storage_field
+        '1')            # stop bit
+    # Bit 15 is 1, which is the first cpu_field. The cpu_field should be decoded
+    # as b01 = 1.
+    self.assertEquals(1, bom.encoded_fields['cpu'])
+    self.assertEquals(0, bom.encoded_fields['storage'])
+
+    # Test with 20 bits here. This should be regarded as an incomplete bit chunk
+    # for storage_field, i.e. storage_field was previously one bit (and some
+    # HWIDs were generated) but it has since been extended to two bits.
+    bom = BinaryStringToBOM(
+        self.database,
+        '00000'         # image ID
+        '0000111101000'  # 12 bits, up through second cpu_field
+        '1'             # the incomplete storage_field chunk
+        '1')            # stop bit
+    # The cpu_field should still be b01 = 1.
+    self.assertEquals(1, bom.encoded_fields['cpu'])
+    # The 1 in the incomplete two-bit storage field should be decoded as b1 = 1
+    # instead of b10 = 2.
+    self.assertEquals(1, bom.encoded_fields['storage'])
+
+  def testDecode(self):
+    reference_bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                            self.results[0])
+    self.database.UpdateComponentsOfBOM(reference_bom, {
+        'keyboard': 'keyboard_us', 'dram': 'dram_0',
+        'display_panel': 'display_panel_0'})
+    hwid = Decode(self.database, 'CHROMEBOOK AA5A-Y6L')
+    self.assertEquals('0000000000111010000011', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK AA5A-Y6L', hwid.encoded_string)
+    self._CheckBOM(reference_bom, hwid.bom)
+
+    hwid = Decode(self.database, 'CHROMEBOOK C2H-I3Q-A6Q')
+    self.assertEquals('0001000000111010000011', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK C2H-I3Q-A6Q', hwid.encoded_string)
+    reference_bom.image_id = 2
+    self._CheckBOM(reference_bom, hwid.bom)
+
+  def testPreviousVersionOfEncodedString(self):
+    bom = BinaryStringToBOM(self.database, '000000000011101000001')
+    self.assertEquals(1, bom.encoded_fields['cpu'])
+    hwid = Decode(self.database, 'CHROMEBOOK AA5A-Q7Z')
+    self.assertEquals('000000000011101000001', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK AA5A-Q7Z', hwid.encoded_string)
+    self.assertEquals(1, hwid.bom.encoded_fields['cpu'])
+
+  def testDecodeRegion(self):
+    db = Database.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_db_regions.yaml'))
+    hwid = Decode(db, 'CHROMEBOOK A25-Q22')
+    # The BOM should load 'us' region from the probe result (numeric_id=29).)
+    self.assertEquals(29, hwid.bom.encoded_fields['region_field'])
+    self.assertEquals(
+        [('us', {'region_code': Value('us')}, None)],
+        hwid.bom.components['region'])
+
+
+class EncoderTest(unittest.TestCase):
+
+  def setUp(self):
+    self.database = Database.LoadFile(os.path.join(_TEST_DATA_PATH,
+                                                   'test_db.yaml'))
+    self.results = json_utils.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_probe_result.json'))
+
+  def testBOMToBinaryString(self):
+    bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                  self.results[0])
+    # Manually set unprobeable components.
+    self.database.UpdateComponentsOfBOM(bom, {
+        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
+    self.assertEquals(
+        '0000000000111010000011', BOMToBinaryString(self.database, bom))
+    # Change firmware's encoded index to 1.
+    mocked_bom = bom.Duplicate()
+    self.database.UpdateComponentsOfBOM(
+        mocked_bom, {'ro_main_firmware': 'ro_main_firmware_1'})
+    self.assertEquals(
+        '0000000001111010000011', BOMToBinaryString(self.database, mocked_bom))
+    # Change image id to 2.
+    mocked_bom.image_id = 2
+    self.assertEquals(
+        '0001000001111010000011', BOMToBinaryString(self.database, mocked_bom))
+    # Change encoding pattern index to 1.
+    mocked_bom.encoding_pattern_index = 1
+    self.assertEquals(
+        '1001000001111010000011', BOMToBinaryString(self.database, mocked_bom))
+
+  def testBinaryStringToEncodedString(self):
+    self.assertEquals('CHROMEBOOK A5AU-LU',
+                      BinaryStringToEncodedString(
+                          self.database, '000001110100000101'))
+    self.assertEquals('CHROMEBOOK C9I-F4N',
+                      BinaryStringToEncodedString(
+                          self.database, '000101110100000101'))
+
+  def testEncode(self):
+    bom = hwid_utils.GenerateBOMFromProbedResults(self.database,
+                                                  self.results[0])
+    # Manually set unprobeable components.
+    self.database.UpdateComponentsOfBOM(bom, {
+        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
+    bom.image_id = 0
+    hwid = Encode(self.database, bom)
+    self.assertEquals('0000000000111010000011', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK AA5A-Y6L', hwid.encoded_string)
+
+    bom.image_id = 2
+    hwid = Encode(self.database, bom)
+    self.assertEquals('0001000000111010000011', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK C2H-I3Q-A6Q', hwid.encoded_string)
+
+  def testEncodeError(self):
+    # Missing required component 'dram'.
+    mock_results = copy.deepcopy(self.results[0])
+    mock_results.pop('dram')
+    bom = hwid_utils.GenerateBOMFromProbedResults(self.database, mock_results)
+    self.database.UpdateComponentsOfBOM(bom, {
+        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
+    self.assertRaisesRegexp(
+        HWIDException, "Encoded fields 'dram' have unknown indices",
+        Encode, self.database, bom)
+
+    # Unsupported probe values of component 'dram'.
+    mock_results = copy.deepcopy(self.results[0])
+    mock_results['dram'] = {'generic': [{'vendor': 'FOO', 'size': '4G'}]}
+    bom = hwid_utils.GenerateBOMFromProbedResults(self.database, mock_results)
+    self.database.UpdateComponentsOfBOM(bom, {
+        'keyboard': 'keyboard_us', 'display_panel': 'display_panel_0'})
+    self.assertRaisesRegexp(
+        HWIDException, "Unknown component values: "
+        "\"dram:{'size': '4G', 'vendor': 'FOO'}\"",
+        Encode, self.database, bom)
+
+  def testEncodeRegion(self):
+    db = Database.LoadFile(
+        os.path.join(_TEST_DATA_PATH, 'test_db_regions.yaml'))
+    bom = hwid_utils.GenerateBOMFromProbedResults(db, self.results[5])
+    # The BOM should load 'us' region from the probe result (numeric_id=29).)
+    self.assertEquals(29, bom.encoded_fields['region_field'])
+    hwid = Encode(db, bom)
+    # The encoder should encode field index 29 into the region field.
+    self.assertEquals('00000000111011', hwid.binary_string)
+    self.assertEquals('CHROMEBOOK A25-Q22', hwid.encoded_string)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/py/hwid/v3/valid_hwid_db_unittest.py b/py/hwid/v3/valid_hwid_db_unittest.py
index 7dac596..ba854c3 100755
--- a/py/hwid/v3/valid_hwid_db_unittest.py
+++ b/py/hwid/v3/valid_hwid_db_unittest.py
@@ -299,8 +299,9 @@
       hwid = hwid_utils.GenerateHWID(db, bom, device_info,
                                      vpd=vpd, rma_mode=rma_mode)
       # Test all rules.
-      db.rules.EvaluateRules(Context(hwid=hwid, vpd=vpd,
-                                     device_info=device_info))
+      db.rules.EvaluateRules(Context(
+          database=hwid.database, bom=hwid.bom, mode=hwid.mode, vpd=vpd,
+          device_info=device_info))
       return hwid
 
     if error:
diff --git a/py/test/event_log_unittest.py b/py/test/event_log_unittest.py
index 8b98046..9719aa6 100755
--- a/py/test/event_log_unittest.py
+++ b/py/test/event_log_unittest.py
@@ -23,7 +23,6 @@
 import yaml
 
 import factory_common  # pylint: disable=unused-import
-from cros.factory.hwid.v3.common import ProbedComponentResult
 from cros.factory.test import event_log
 from cros.factory.test import session
 from cros.factory.utils import file_utils
@@ -34,6 +33,9 @@
                      '[a-f0-9]{4}-[a-f0-9]{12}$')
 
 
+_TestNamedTuple = collections.namedtuple('_TestNamedTuple', ['a', 'b', 'c'])
+
+
 def Reset():
   # Deletes state files and resets global variables.
   event_log.device_id = event_log.reimage_id = None
@@ -92,7 +94,7 @@
                      dump(collections.OrderedDict([('bar', 1), ('foo', 3)])))
     # A subclass of a list
     self.assertEqual('\n'.join(['- comp_foo', '- value_foo', '- null']),
-                     dump(ProbedComponentResult('comp_foo', 'value_foo', None)))
+                     dump(_TestNamedTuple('comp_foo', 'value_foo', None)))
     # Tuple type
     self.assertEqual('\n'.join(['- v1', '- v2', '- v3']),
                      dump(('v1', 'v2', 'v3')))
diff --git a/py/test/pytests/hwid.py b/py/test/pytests/hwid.py
index 282693a..508bd22 100644
--- a/py/test/pytests/hwid.py
+++ b/py/test/pytests/hwid.py
@@ -82,12 +82,12 @@
 # If present, these files will override the project and probe results
 # (for testing).
 OVERRIDE_PROJECT_PATH = os.path.join(
-    hwid_utils.DEFAULT_HWID_DATA_PATH, 'OVERRIDE_PROJECT')
+    hwid_utils.GetDefaultDataPath(), 'OVERRIDE_PROJECT')
 # OVERRIDE_PROBED_RESULTS should be generated with:
 #    `gooftool probe`
 # to include all the VPD in it.
 OVERRIDE_PROBED_RESULTS_PATH = os.path.join(
-    hwid_utils.DEFAULT_HWID_DATA_PATH, 'OVERRIDE_PROBED_RESULTS')
+    hwid_utils.GetDefaultDataPath(), 'OVERRIDE_PROBED_RESULTS')
 
 
 class HWIDV3Test(test_ui.TestCaseWithUI):