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 update_payload import update_metadata_pb2
     10 from update_payload.error import PayloadError
     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 BROTLI_BSDIFF_MINOR_PAYLOAD_VERSION = 4
     31 PUFFDIFF_MINOR_PAYLOAD_VERSION = 5
     32 
     33 #
     34 # Payload operation types.
     35 #
     36 class OpType(object):
     37   """Container for operation type constants."""
     38   _CLASS = update_metadata_pb2.InstallOperation
     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   PUFFDIFF = _CLASS.PUFFDIFF
     49   BROTLI_BSDIFF = _CLASS.BROTLI_BSDIFF
     50   ALL = (REPLACE, REPLACE_BZ, MOVE, BSDIFF, SOURCE_COPY, SOURCE_BSDIFF, ZERO,
     51          DISCARD, REPLACE_XZ, PUFFDIFF, BROTLI_BSDIFF)
     52   NAMES = {
     53       REPLACE: 'REPLACE',
     54       REPLACE_BZ: 'REPLACE_BZ',
     55       MOVE: 'MOVE',
     56       BSDIFF: 'BSDIFF',
     57       SOURCE_COPY: 'SOURCE_COPY',
     58       SOURCE_BSDIFF: 'SOURCE_BSDIFF',
     59       ZERO: 'ZERO',
     60       DISCARD: 'DISCARD',
     61       REPLACE_XZ: 'REPLACE_XZ',
     62       PUFFDIFF: 'PUFFDIFF',
     63       BROTLI_BSDIFF: 'BROTLI_BSDIFF',
     64   }
     65 
     66   def __init__(self):
     67     pass
     68 
     69 
     70 #
     71 # Checked and hashed reading of data.
     72 #
     73 def IntPackingFmtStr(size, is_unsigned):
     74   """Returns an integer format string for use by the struct module.
     75 
     76   Args:
     77     size: the integer size in bytes (2, 4 or 8)
     78     is_unsigned: whether it is signed or not
     79 
     80   Returns:
     81     A format string for packing/unpacking integer values; assumes network byte
     82     order (big-endian).
     83 
     84   Raises:
     85     PayloadError if something is wrong with the arguments.
     86   """
     87   # Determine the base conversion format.
     88   if size == 2:
     89     fmt = 'h'
     90   elif size == 4:
     91     fmt = 'i'
     92   elif size == 8:
     93     fmt = 'q'
     94   else:
     95     raise PayloadError('unsupport numeric field size (%s)' % size)
     96 
     97   # Signed or unsigned?
     98   if is_unsigned:
     99     fmt = fmt.upper()
    100 
    101   # Make it network byte order (big-endian).
    102   fmt = '!' + fmt
    103 
    104   return fmt
    105 
    106 
    107 def Read(file_obj, length, offset=None, hasher=None):
    108   """Reads binary data from a file.
    109 
    110   Args:
    111     file_obj: an open file object
    112     length: the length of the data to read
    113     offset: an offset to seek to prior to reading; this is an absolute offset
    114             from either the beginning (non-negative) or end (negative) of the
    115             file.  (optional)
    116     hasher: a hashing object to pass the read data through (optional)
    117 
    118   Returns:
    119     A string containing the read data.
    120 
    121   Raises:
    122     PayloadError if a read error occurred or not enough data was read.
    123   """
    124   if offset is not None:
    125     if offset >= 0:
    126       file_obj.seek(offset)
    127     else:
    128       file_obj.seek(offset, 2)
    129 
    130   try:
    131     data = file_obj.read(length)
    132   except IOError, e:
    133     raise PayloadError('error reading from file (%s): %s' % (file_obj.name, e))
    134 
    135   if len(data) != length:
    136     raise PayloadError(
    137         'reading from file (%s) too short (%d instead of %d bytes)' %
    138         (file_obj.name, len(data), length))
    139 
    140   if hasher:
    141     hasher.update(data)
    142 
    143   return data
    144 
    145 
    146 #
    147 # Formatting functions.
    148 #
    149 def FormatExtent(ex, block_size=0):
    150   end_block = ex.start_block + ex.num_blocks
    151   if block_size:
    152     return '%d->%d * %d' % (ex.start_block, end_block, block_size)
    153   else:
    154     return '%d->%d' % (ex.start_block, end_block)
    155 
    156 
    157 def FormatSha256(digest):
    158   """Returns a canonical string representation of a SHA256 digest."""
    159   return digest.encode('base64').strip()
    160 
    161 
    162 #
    163 # Useful iterators.
    164 #
    165 def _ObjNameIter(items, base_name, reverse=False, name_format_func=None):
    166   """A generic (item, name) tuple iterators.
    167 
    168   Args:
    169     items: the sequence of objects to iterate on
    170     base_name: the base name for all objects
    171     reverse: whether iteration should be in reverse order
    172     name_format_func: a function to apply to the name string
    173 
    174   Yields:
    175     An iterator whose i-th invocation returns (items[i], name), where name ==
    176     base_name + '[i]' (with a formatting function optionally applied to it).
    177   """
    178   idx, inc = (len(items), -1) if reverse else (1, 1)
    179   if reverse:
    180     items = reversed(items)
    181   for item in items:
    182     item_name = '%s[%d]' % (base_name, idx)
    183     if name_format_func:
    184       item_name = name_format_func(item, item_name)
    185     yield (item, item_name)
    186     idx += inc
    187 
    188 
    189 def _OperationNameFormatter(op, op_name):
    190   return '%s(%s)' % (op_name, OpType.NAMES.get(op.type, '?'))
    191 
    192 
    193 def OperationIter(operations, base_name, reverse=False):
    194   """An (item, name) iterator for update operations."""
    195   return _ObjNameIter(operations, base_name, reverse=reverse,
    196                       name_format_func=_OperationNameFormatter)
    197 
    198 
    199 def ExtentIter(extents, base_name, reverse=False):
    200   """An (item, name) iterator for operation extents."""
    201   return _ObjNameIter(extents, base_name, reverse=reverse)
    202 
    203 
    204 def SignatureIter(sigs, base_name, reverse=False):
    205   """An (item, name) iterator for signatures."""
    206   return _ObjNameIter(sigs, base_name, reverse=reverse)
    207