Home | History | Annotate | Download | only in update_payload
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Utilities for update payload processing."""
      6 
      7 from __future__ import print_function
      8 
      9 from error import PayloadError
     10 import update_metadata_pb2
     11 
     12 
     13 #
     14 # Constants.
     15 #
     16 PSEUDO_EXTENT_MARKER = (1L << 64) - 1  # UINT64_MAX
     17 
     18 SIG_ASN1_HEADER = (
     19     '\x30\x31\x30\x0d\x06\x09\x60\x86'
     20     '\x48\x01\x65\x03\x04\x02\x01\x05'
     21     '\x00\x04\x20'
     22 )
     23 
     24 CHROMEOS_MAJOR_PAYLOAD_VERSION = 1
     25 BRILLO_MAJOR_PAYLOAD_VERSION = 2
     26 
     27 INPLACE_MINOR_PAYLOAD_VERSION = 1
     28 SOURCE_MINOR_PAYLOAD_VERSION = 2
     29 OPSRCHASH_MINOR_PAYLOAD_VERSION = 3
     30 IMGDIFF_MINOR_PAYLOAD_VERSION = 4
     31 
     32 #
     33 # Payload operation types.
     34 #
     35 class OpType(object):
     36   """Container for operation type constants."""
     37   _CLASS = update_metadata_pb2.InstallOperation
     38   # pylint: disable=E1101
     39   REPLACE = _CLASS.REPLACE
     40   REPLACE_BZ = _CLASS.REPLACE_BZ
     41   MOVE = _CLASS.MOVE
     42   BSDIFF = _CLASS.BSDIFF
     43   SOURCE_COPY = _CLASS.SOURCE_COPY
     44   SOURCE_BSDIFF = _CLASS.SOURCE_BSDIFF
     45   ZERO = _CLASS.ZERO
     46   DISCARD = _CLASS.DISCARD
     47   REPLACE_XZ = _CLASS.REPLACE_XZ
     48   IMGDIFF = _CLASS.IMGDIFF
     49   ALL = (REPLACE, REPLACE_BZ, MOVE, BSDIFF, SOURCE_COPY, SOURCE_BSDIFF, ZERO,
     50          DISCARD, REPLACE_XZ, IMGDIFF)
     51   NAMES = {
     52       REPLACE: 'REPLACE',
     53       REPLACE_BZ: 'REPLACE_BZ',
     54       MOVE: 'MOVE',
     55       BSDIFF: 'BSDIFF',
     56       SOURCE_COPY: 'SOURCE_COPY',
     57       SOURCE_BSDIFF: 'SOURCE_BSDIFF',
     58       ZERO: 'ZERO',
     59       DISCARD: 'DISCARD',
     60       REPLACE_XZ: 'REPLACE_XZ',
     61       IMGDIFF: 'IMGDIFF',
     62   }
     63 
     64   def __init__(self):
     65     pass
     66 
     67 
     68 #
     69 # Checked and hashed reading of data.
     70 #
     71 def IntPackingFmtStr(size, is_unsigned):
     72   """Returns an integer format string for use by the struct module.
     73 
     74   Args:
     75     size: the integer size in bytes (2, 4 or 8)
     76     is_unsigned: whether it is signed or not
     77 
     78   Returns:
     79     A format string for packing/unpacking integer values; assumes network byte
     80     order (big-endian).
     81 
     82   Raises:
     83     PayloadError if something is wrong with the arguments.
     84   """
     85   # Determine the base conversion format.
     86   if size == 2:
     87     fmt = 'h'
     88   elif size == 4:
     89     fmt = 'i'
     90   elif size == 8:
     91     fmt = 'q'
     92   else:
     93     raise PayloadError('unsupport numeric field size (%s)' % size)
     94 
     95   # Signed or unsigned?
     96   if is_unsigned:
     97     fmt = fmt.upper()
     98 
     99   # Make it network byte order (big-endian).
    100   fmt = '!' + fmt
    101 
    102   return fmt
    103 
    104 
    105 def Read(file_obj, length, offset=None, hasher=None):
    106   """Reads binary data from a file.
    107 
    108   Args:
    109     file_obj: an open file object
    110     length: the length of the data to read
    111     offset: an offset to seek to prior to reading; this is an absolute offset
    112             from either the beginning (non-negative) or end (negative) of the
    113             file.  (optional)
    114     hasher: a hashing object to pass the read data through (optional)
    115 
    116   Returns:
    117     A string containing the read data.
    118 
    119   Raises:
    120     PayloadError if a read error occurred or not enough data was read.
    121   """
    122   if offset is not None:
    123     if offset >= 0:
    124       file_obj.seek(offset)
    125     else:
    126       file_obj.seek(offset, 2)
    127 
    128   try:
    129     data = file_obj.read(length)
    130   except IOError, e:
    131     raise PayloadError('error reading from file (%s): %s' % (file_obj.name, e))
    132 
    133   if len(data) != length:
    134     raise PayloadError(
    135         'reading from file (%s) too short (%d instead of %d bytes)' %
    136         (file_obj.name, len(data), length))
    137 
    138   if hasher:
    139     hasher.update(data)
    140 
    141   return data
    142 
    143 
    144 #
    145 # Formatting functions.
    146 #
    147 def FormatExtent(ex, block_size=0):
    148   end_block = ex.start_block + ex.num_blocks
    149   if block_size:
    150     return '%d->%d * %d' % (ex.start_block, end_block, block_size)
    151   else:
    152     return '%d->%d' % (ex.start_block, end_block)
    153 
    154 
    155 def FormatSha256(digest):
    156   """Returns a canonical string representation of a SHA256 digest."""
    157   return digest.encode('base64').strip()
    158 
    159 
    160 #
    161 # Useful iterators.
    162 #
    163 def _ObjNameIter(items, base_name, reverse=False, name_format_func=None):
    164   """A generic (item, name) tuple iterators.
    165 
    166   Args:
    167     items: the sequence of objects to iterate on
    168     base_name: the base name for all objects
    169     reverse: whether iteration should be in reverse order
    170     name_format_func: a function to apply to the name string
    171 
    172   Yields:
    173     An iterator whose i-th invocation returns (items[i], name), where name ==
    174     base_name + '[i]' (with a formatting function optionally applied to it).
    175   """
    176   idx, inc = (len(items), -1) if reverse else (1, 1)
    177   if reverse:
    178     items = reversed(items)
    179   for item in items:
    180     item_name = '%s[%d]' % (base_name, idx)
    181     if name_format_func:
    182       item_name = name_format_func(item, item_name)
    183     yield (item, item_name)
    184     idx += inc
    185 
    186 
    187 def _OperationNameFormatter(op, op_name):
    188   return '%s(%s)' % (op_name, OpType.NAMES.get(op.type, '?'))
    189 
    190 
    191 def OperationIter(operations, base_name, reverse=False):
    192   """An (item, name) iterator for update operations."""
    193   return _ObjNameIter(operations, base_name, reverse=reverse,
    194                       name_format_func=_OperationNameFormatter)
    195 
    196 
    197 def ExtentIter(extents, base_name, reverse=False):
    198   """An (item, name) iterator for operation extents."""
    199   return _ObjNameIter(extents, base_name, reverse=reverse)
    200 
    201 
    202 def SignatureIter(sigs, base_name, reverse=False):
    203   """An (item, name) iterator for signatures."""
    204   return _ObjNameIter(sigs, base_name, reverse=reverse)
    205