blob: 442fcaa9984f5dddf249e24e538859a2c22baa2a [file] [log] [blame]
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08001#!/usr/bin/python2
Gilad Arnold5502b562013-03-08 13:22:31 -08002#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit testing checker.py."""
8
Gilad Arnoldf583a7d2015-02-05 13:23:55 -08009from __future__ import print_function
10
Gilad Arnold5502b562013-03-08 13:22:31 -080011import array
12import collections
13import cStringIO
14import hashlib
15import itertools
16import os
17import unittest
18
Gilad Arnoldcb638912013-06-24 04:57:11 -070019# pylint cannot find mox.
Gilad Arnold5502b562013-03-08 13:22:31 -080020# pylint: disable=F0401
21import mox
22
23import checker
24import common
Gilad Arnoldcb638912013-06-24 04:57:11 -070025import payload as update_payload # Avoid name conflicts later.
Gilad Arnold5502b562013-03-08 13:22:31 -080026import test_utils
27import update_metadata_pb2
28
29
Gilad Arnold5502b562013-03-08 13:22:31 -080030def _OpTypeByName(op_name):
31 op_name_to_type = {
32 'REPLACE': common.OpType.REPLACE,
33 'REPLACE_BZ': common.OpType.REPLACE_BZ,
34 'MOVE': common.OpType.MOVE,
35 'BSDIFF': common.OpType.BSDIFF,
Allie Woodf5c4f3e2015-02-20 16:57:46 -080036 'SOURCE_COPY': common.OpType.SOURCE_COPY,
37 'SOURCE_BSDIFF': common.OpType.SOURCE_BSDIFF,
Gilad Arnold5502b562013-03-08 13:22:31 -080038 }
39 return op_name_to_type[op_name]
40
41
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070042def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None,
43 checker_init_dargs=None):
Gilad Arnold5502b562013-03-08 13:22:31 -080044 """Returns a payload checker from a given payload generator."""
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070045 if payload_gen_dargs is None:
46 payload_gen_dargs = {}
47 if checker_init_dargs is None:
48 checker_init_dargs = {}
49
Gilad Arnold5502b562013-03-08 13:22:31 -080050 payload_file = cStringIO.StringIO()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070051 payload_gen_write_to_file_func(payload_file, **payload_gen_dargs)
Gilad Arnold5502b562013-03-08 13:22:31 -080052 payload_file.seek(0)
53 payload = update_payload.Payload(payload_file)
54 payload.Init()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070055 return checker.PayloadChecker(payload, **checker_init_dargs)
Gilad Arnold5502b562013-03-08 13:22:31 -080056
57
58def _GetPayloadCheckerWithData(payload_gen):
59 """Returns a payload checker from a given payload generator."""
60 payload_file = cStringIO.StringIO()
61 payload_gen.WriteToFile(payload_file)
62 payload_file.seek(0)
63 payload = update_payload.Payload(payload_file)
64 payload.Init()
65 return checker.PayloadChecker(payload)
66
67
Gilad Arnoldcb638912013-06-24 04:57:11 -070068# This class doesn't need an __init__().
Gilad Arnold5502b562013-03-08 13:22:31 -080069# pylint: disable=W0232
Gilad Arnoldcb638912013-06-24 04:57:11 -070070# Unit testing is all about running protected methods.
Gilad Arnold5502b562013-03-08 13:22:31 -080071# pylint: disable=W0212
Gilad Arnoldcb638912013-06-24 04:57:11 -070072# Don't bark about missing members of classes you cannot import.
Gilad Arnold5502b562013-03-08 13:22:31 -080073# pylint: disable=E1101
74class PayloadCheckerTest(mox.MoxTestBase):
75 """Tests the PayloadChecker class.
76
77 In addition to ordinary testFoo() methods, which are automatically invoked by
78 the unittest framework, in this class we make use of DoBarTest() calls that
79 implement parametric tests of certain features. In order to invoke each test,
80 which embodies a unique combination of parameter values, as a complete unit
81 test, we perform explicit enumeration of the parameter space and create
82 individual invocation contexts for each, which are then bound as
83 testBar__param1=val1__param2=val2(). The enumeration of parameter spaces for
84 all such tests is done in AddAllParametricTests().
Gilad Arnold5502b562013-03-08 13:22:31 -080085 """
86
87 def MockPayload(self):
Allie Woodf5c4f3e2015-02-20 16:57:46 -080088 """Create a mock payload object, complete with a mock manifest."""
Gilad Arnold5502b562013-03-08 13:22:31 -080089 payload = self.mox.CreateMock(update_payload.Payload)
90 payload.is_init = True
91 payload.manifest = self.mox.CreateMock(
92 update_metadata_pb2.DeltaArchiveManifest)
93 return payload
94
95 @staticmethod
96 def NewExtent(start_block, num_blocks):
97 """Returns an Extent message.
98
99 Each of the provided fields is set iff it is >= 0; otherwise, it's left at
100 its default state.
101
102 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700103 start_block: The starting block of the extent.
104 num_blocks: The number of blocks in the extent.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800105
Gilad Arnold5502b562013-03-08 13:22:31 -0800106 Returns:
107 An Extent message.
Gilad Arnold5502b562013-03-08 13:22:31 -0800108 """
109 ex = update_metadata_pb2.Extent()
110 if start_block >= 0:
111 ex.start_block = start_block
112 if num_blocks >= 0:
113 ex.num_blocks = num_blocks
114 return ex
115
116 @staticmethod
117 def NewExtentList(*args):
118 """Returns an list of extents.
119
120 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700121 *args: (start_block, num_blocks) pairs defining the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800122
Gilad Arnold5502b562013-03-08 13:22:31 -0800123 Returns:
124 A list of Extent objects.
Gilad Arnold5502b562013-03-08 13:22:31 -0800125 """
126 ex_list = []
127 for start_block, num_blocks in args:
128 ex_list.append(PayloadCheckerTest.NewExtent(start_block, num_blocks))
129 return ex_list
130
131 @staticmethod
132 def AddToMessage(repeated_field, field_vals):
133 for field_val in field_vals:
134 new_field = repeated_field.add()
135 new_field.CopyFrom(field_val)
136
Gilad Arnold5502b562013-03-08 13:22:31 -0800137 def SetupAddElemTest(self, is_present, is_submsg, convert=str,
138 linebreak=False, indent=0):
139 """Setup for testing of _CheckElem() and its derivatives.
140
141 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700142 is_present: Whether or not the element is found in the message.
143 is_submsg: Whether the element is a sub-message itself.
144 convert: A representation conversion function.
145 linebreak: Whether or not a linebreak is to be used in the report.
146 indent: Indentation used for the report.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800147
Gilad Arnold5502b562013-03-08 13:22:31 -0800148 Returns:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700149 msg: A mock message object.
150 report: A mock report object.
151 subreport: A mock sub-report object.
152 name: An element name to check.
153 val: Expected element value.
Gilad Arnold5502b562013-03-08 13:22:31 -0800154 """
155 name = 'foo'
156 val = 'fake submsg' if is_submsg else 'fake field'
157 subreport = 'fake subreport'
158
159 # Create a mock message.
160 msg = self.mox.CreateMock(update_metadata_pb2.message.Message)
161 msg.HasField(name).AndReturn(is_present)
162 setattr(msg, name, val)
163
164 # Create a mock report.
165 report = self.mox.CreateMock(checker._PayloadReport)
166 if is_present:
167 if is_submsg:
168 report.AddSubReport(name).AndReturn(subreport)
169 else:
170 report.AddField(name, convert(val), linebreak=linebreak, indent=indent)
171
172 self.mox.ReplayAll()
173 return (msg, report, subreport, name, val)
174
175 def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert,
176 linebreak, indent):
177 """Parametric testing of _CheckElem().
178
179 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700180 is_present: Whether or not the element is found in the message.
181 is_mandatory: Whether or not it's a mandatory element.
182 is_submsg: Whether the element is a sub-message itself.
183 convert: A representation conversion function.
184 linebreak: Whether or not a linebreak is to be used in the report.
185 indent: Indentation used for the report.
Gilad Arnold5502b562013-03-08 13:22:31 -0800186 """
187 msg, report, subreport, name, val = self.SetupAddElemTest(
188 is_present, is_submsg, convert, linebreak, indent)
189
Gilad Arnoldcb638912013-06-24 04:57:11 -0700190 args = (msg, name, report, is_mandatory, is_submsg)
191 kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
Gilad Arnold5502b562013-03-08 13:22:31 -0800192 if is_mandatory and not is_present:
193 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700194 checker.PayloadChecker._CheckElem, *args, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -0800195 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700196 ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args,
197 **kwargs)
198 self.assertEquals(val if is_present else None, ret_val)
199 self.assertEquals(subreport if is_present and is_submsg else None,
200 ret_subreport)
Gilad Arnold5502b562013-03-08 13:22:31 -0800201
202 def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak,
203 indent):
204 """Parametric testing of _Check{Mandatory,Optional}Field().
205
206 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700207 is_mandatory: Whether we're testing a mandatory call.
208 is_present: Whether or not the element is found in the message.
209 convert: A representation conversion function.
210 linebreak: Whether or not a linebreak is to be used in the report.
211 indent: Indentation used for the report.
Gilad Arnold5502b562013-03-08 13:22:31 -0800212 """
213 msg, report, _, name, val = self.SetupAddElemTest(
214 is_present, False, convert, linebreak, indent)
215
216 # Prepare for invocation of the tested method.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700217 args = [msg, name, report]
218 kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
Gilad Arnold5502b562013-03-08 13:22:31 -0800219 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700220 args.append('bar')
Gilad Arnold5502b562013-03-08 13:22:31 -0800221 tested_func = checker.PayloadChecker._CheckMandatoryField
222 else:
223 tested_func = checker.PayloadChecker._CheckOptionalField
224
225 # Test the method call.
226 if is_mandatory and not is_present:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700227 self.assertRaises(update_payload.PayloadError, tested_func, *args,
228 **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -0800229 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700230 ret_val = tested_func(*args, **kwargs)
231 self.assertEquals(val if is_present else None, ret_val)
Gilad Arnold5502b562013-03-08 13:22:31 -0800232
233 def DoAddSubMsgTest(self, is_mandatory, is_present):
234 """Parametrized testing of _Check{Mandatory,Optional}SubMsg().
235
236 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700237 is_mandatory: Whether we're testing a mandatory call.
238 is_present: Whether or not the element is found in the message.
Gilad Arnold5502b562013-03-08 13:22:31 -0800239 """
240 msg, report, subreport, name, val = self.SetupAddElemTest(is_present, True)
241
242 # Prepare for invocation of the tested method.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700243 args = [msg, name, report]
Gilad Arnold5502b562013-03-08 13:22:31 -0800244 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700245 args.append('bar')
Gilad Arnold5502b562013-03-08 13:22:31 -0800246 tested_func = checker.PayloadChecker._CheckMandatorySubMsg
247 else:
248 tested_func = checker.PayloadChecker._CheckOptionalSubMsg
249
250 # Test the method call.
251 if is_mandatory and not is_present:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700252 self.assertRaises(update_payload.PayloadError, tested_func, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800253 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700254 ret_val, ret_subreport = tested_func(*args)
255 self.assertEquals(val if is_present else None, ret_val)
256 self.assertEquals(subreport if is_present else None, ret_subreport)
Gilad Arnold5502b562013-03-08 13:22:31 -0800257
258 def testCheckPresentIff(self):
259 """Tests _CheckPresentIff()."""
260 self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
261 None, None, 'foo', 'bar', 'baz'))
262 self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
263 'a', 'b', 'foo', 'bar', 'baz'))
264 self.assertRaises(update_payload.PayloadError,
265 checker.PayloadChecker._CheckPresentIff,
266 'a', None, 'foo', 'bar', 'baz')
267 self.assertRaises(update_payload.PayloadError,
268 checker.PayloadChecker._CheckPresentIff,
269 None, 'b', 'foo', 'bar', 'baz')
270
271 def DoCheckSha256SignatureTest(self, expect_pass, expect_subprocess_call,
272 sig_data, sig_asn1_header,
273 returned_signed_hash, expected_signed_hash):
274 """Parametric testing of _CheckSha256SignatureTest().
275
276 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700277 expect_pass: Whether or not it should pass.
278 expect_subprocess_call: Whether to expect the openssl call to happen.
279 sig_data: The signature raw data.
280 sig_asn1_header: The ASN1 header.
281 returned_signed_hash: The signed hash data retuned by openssl.
282 expected_signed_hash: The signed hash data to compare against.
Gilad Arnold5502b562013-03-08 13:22:31 -0800283 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700284 try:
285 # Stub out the subprocess invocation.
286 self.mox.StubOutWithMock(checker.PayloadChecker, '_Run')
287 if expect_subprocess_call:
288 checker.PayloadChecker._Run(
289 mox.IsA(list), send_data=sig_data).AndReturn(
290 (sig_asn1_header + returned_signed_hash, None))
Gilad Arnold5502b562013-03-08 13:22:31 -0800291
Gilad Arnoldcb638912013-06-24 04:57:11 -0700292 self.mox.ReplayAll()
293 if expect_pass:
294 self.assertIsNone(checker.PayloadChecker._CheckSha256Signature(
295 sig_data, 'foo', expected_signed_hash, 'bar'))
296 else:
297 self.assertRaises(update_payload.PayloadError,
298 checker.PayloadChecker._CheckSha256Signature,
299 sig_data, 'foo', expected_signed_hash, 'bar')
300 finally:
301 self.mox.UnsetStubs()
Gilad Arnold5502b562013-03-08 13:22:31 -0800302
303 def testCheckSha256Signature_Pass(self):
304 """Tests _CheckSha256Signature(); pass case."""
305 sig_data = 'fake-signature'.ljust(256)
306 signed_hash = hashlib.sha256('fake-data').digest()
307 self.DoCheckSha256SignatureTest(True, True, sig_data,
308 common.SIG_ASN1_HEADER, signed_hash,
309 signed_hash)
310
311 def testCheckSha256Signature_FailBadSignature(self):
312 """Tests _CheckSha256Signature(); fails due to malformed signature."""
Gilad Arnoldcb638912013-06-24 04:57:11 -0700313 sig_data = 'fake-signature' # Malformed (not 256 bytes in length).
Gilad Arnold5502b562013-03-08 13:22:31 -0800314 signed_hash = hashlib.sha256('fake-data').digest()
315 self.DoCheckSha256SignatureTest(False, False, sig_data,
316 common.SIG_ASN1_HEADER, signed_hash,
317 signed_hash)
318
319 def testCheckSha256Signature_FailBadOutputLength(self):
320 """Tests _CheckSha256Signature(); fails due to unexpected output length."""
321 sig_data = 'fake-signature'.ljust(256)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700322 signed_hash = 'fake-hash' # Malformed (not 32 bytes in length).
Gilad Arnold5502b562013-03-08 13:22:31 -0800323 self.DoCheckSha256SignatureTest(False, True, sig_data,
324 common.SIG_ASN1_HEADER, signed_hash,
325 signed_hash)
326
327 def testCheckSha256Signature_FailBadAsnHeader(self):
328 """Tests _CheckSha256Signature(); fails due to bad ASN1 header."""
329 sig_data = 'fake-signature'.ljust(256)
330 signed_hash = hashlib.sha256('fake-data').digest()
331 bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER))
332 self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header,
333 signed_hash, signed_hash)
334
335 def testCheckSha256Signature_FailBadHash(self):
336 """Tests _CheckSha256Signature(); fails due to bad hash returned."""
337 sig_data = 'fake-signature'.ljust(256)
338 expected_signed_hash = hashlib.sha256('fake-data').digest()
339 returned_signed_hash = hashlib.sha256('bad-fake-data').digest()
340 self.DoCheckSha256SignatureTest(False, True, sig_data,
341 common.SIG_ASN1_HEADER,
342 expected_signed_hash, returned_signed_hash)
343
344 def testCheckBlocksFitLength_Pass(self):
345 """Tests _CheckBlocksFitLength(); pass case."""
346 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
347 64, 4, 16, 'foo'))
348 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
349 60, 4, 16, 'foo'))
350 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
351 49, 4, 16, 'foo'))
352 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
353 48, 3, 16, 'foo'))
354
355 def testCheckBlocksFitLength_TooManyBlocks(self):
356 """Tests _CheckBlocksFitLength(); fails due to excess blocks."""
357 self.assertRaises(update_payload.PayloadError,
358 checker.PayloadChecker._CheckBlocksFitLength,
359 64, 5, 16, 'foo')
360 self.assertRaises(update_payload.PayloadError,
361 checker.PayloadChecker._CheckBlocksFitLength,
362 60, 5, 16, 'foo')
363 self.assertRaises(update_payload.PayloadError,
364 checker.PayloadChecker._CheckBlocksFitLength,
365 49, 5, 16, 'foo')
366 self.assertRaises(update_payload.PayloadError,
367 checker.PayloadChecker._CheckBlocksFitLength,
368 48, 4, 16, 'foo')
369
370 def testCheckBlocksFitLength_TooFewBlocks(self):
371 """Tests _CheckBlocksFitLength(); fails due to insufficient blocks."""
372 self.assertRaises(update_payload.PayloadError,
373 checker.PayloadChecker._CheckBlocksFitLength,
374 64, 3, 16, 'foo')
375 self.assertRaises(update_payload.PayloadError,
376 checker.PayloadChecker._CheckBlocksFitLength,
377 60, 3, 16, 'foo')
378 self.assertRaises(update_payload.PayloadError,
379 checker.PayloadChecker._CheckBlocksFitLength,
380 49, 3, 16, 'foo')
381 self.assertRaises(update_payload.PayloadError,
382 checker.PayloadChecker._CheckBlocksFitLength,
383 48, 2, 16, 'foo')
384
385 def DoCheckManifestTest(self, fail_mismatched_block_size, fail_bad_sigs,
386 fail_mismatched_oki_ori, fail_bad_oki, fail_bad_ori,
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800387 fail_bad_nki, fail_bad_nri, fail_old_kernel_fs_size,
388 fail_old_rootfs_fs_size, fail_new_kernel_fs_size,
389 fail_new_rootfs_fs_size):
Gilad Arnold5502b562013-03-08 13:22:31 -0800390 """Parametric testing of _CheckManifest().
391
392 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700393 fail_mismatched_block_size: Simulate a missing block_size field.
394 fail_bad_sigs: Make signatures descriptor inconsistent.
395 fail_mismatched_oki_ori: Make old rootfs/kernel info partially present.
396 fail_bad_oki: Tamper with old kernel info.
397 fail_bad_ori: Tamper with old rootfs info.
398 fail_bad_nki: Tamper with new kernel info.
399 fail_bad_nri: Tamper with new rootfs info.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700400 fail_old_kernel_fs_size: Make old kernel fs size too big.
401 fail_old_rootfs_fs_size: Make old rootfs fs size too big.
402 fail_new_kernel_fs_size: Make new kernel fs size too big.
403 fail_new_rootfs_fs_size: Make new rootfs fs size too big.
Gilad Arnold5502b562013-03-08 13:22:31 -0800404 """
405 # Generate a test payload. For this test, we only care about the manifest
406 # and don't need any data blobs, hence we can use a plain paylaod generator
407 # (which also gives us more control on things that can be screwed up).
408 payload_gen = test_utils.PayloadGenerator()
409
410 # Tamper with block size, if required.
411 if fail_mismatched_block_size:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700412 payload_gen.SetBlockSize(test_utils.KiB(1))
Gilad Arnold5502b562013-03-08 13:22:31 -0800413 else:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700414 payload_gen.SetBlockSize(test_utils.KiB(4))
Gilad Arnold5502b562013-03-08 13:22:31 -0800415
416 # Add some operations.
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800417 payload_gen.AddOperation(False, common.OpType.MOVE,
418 src_extents=[(0, 16), (16, 497)],
419 dst_extents=[(16, 496), (0, 16)])
420 payload_gen.AddOperation(True, common.OpType.MOVE,
421 src_extents=[(0, 8), (8, 8)],
422 dst_extents=[(8, 8), (0, 8)])
Gilad Arnold5502b562013-03-08 13:22:31 -0800423
424 # Set an invalid signatures block (offset but no size), if required.
425 if fail_bad_sigs:
426 payload_gen.SetSignatures(32, None)
427
Gilad Arnold382df5c2013-05-03 12:49:28 -0700428 # Set partition / filesystem sizes.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700429 rootfs_part_size = test_utils.MiB(8)
430 kernel_part_size = test_utils.KiB(512)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700431 old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size
432 old_kernel_fs_size = new_kernel_fs_size = kernel_part_size
433 if fail_old_kernel_fs_size:
434 old_kernel_fs_size += 100
435 if fail_old_rootfs_fs_size:
436 old_rootfs_fs_size += 100
437 if fail_new_kernel_fs_size:
438 new_kernel_fs_size += 100
439 if fail_new_rootfs_fs_size:
440 new_rootfs_fs_size += 100
441
Gilad Arnold5502b562013-03-08 13:22:31 -0800442 # Add old kernel/rootfs partition info, as required.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700443 if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki:
Gilad Arnold5502b562013-03-08 13:22:31 -0800444 oki_hash = (None if fail_bad_oki
445 else hashlib.sha256('fake-oki-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700446 payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash)
447 if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or
448 fail_bad_ori):
449 ori_hash = (None if fail_bad_ori
450 else hashlib.sha256('fake-ori-content').digest())
451 payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash)
Gilad Arnold5502b562013-03-08 13:22:31 -0800452
453 # Add new kernel/rootfs partition info.
454 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700455 True, True, new_kernel_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800456 None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest())
457 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700458 False, True, new_rootfs_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800459 None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest())
460
461 # Create the test object.
462 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile)
463 report = checker._PayloadReport()
464
465 should_fail = (fail_mismatched_block_size or fail_bad_sigs or
466 fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800467 fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or
468 fail_old_rootfs_fs_size or fail_new_kernel_fs_size or
469 fail_new_rootfs_fs_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800470 if should_fail:
471 self.assertRaises(update_payload.PayloadError,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700472 payload_checker._CheckManifest, report,
473 rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800474 else:
Gilad Arnold382df5c2013-05-03 12:49:28 -0700475 self.assertIsNone(payload_checker._CheckManifest(report,
476 rootfs_part_size,
477 kernel_part_size))
Gilad Arnold5502b562013-03-08 13:22:31 -0800478
479 def testCheckLength(self):
480 """Tests _CheckLength()."""
481 payload_checker = checker.PayloadChecker(self.MockPayload())
482 block_size = payload_checker.block_size
483
484 # Passes.
485 self.assertIsNone(payload_checker._CheckLength(
486 int(3.5 * block_size), 4, 'foo', 'bar'))
487 # Fails, too few blocks.
488 self.assertRaises(update_payload.PayloadError,
489 payload_checker._CheckLength,
490 int(3.5 * block_size), 3, 'foo', 'bar')
491 # Fails, too many blocks.
492 self.assertRaises(update_payload.PayloadError,
493 payload_checker._CheckLength,
494 int(3.5 * block_size), 5, 'foo', 'bar')
495
496 def testCheckExtents(self):
497 """Tests _CheckExtents()."""
498 payload_checker = checker.PayloadChecker(self.MockPayload())
499 block_size = payload_checker.block_size
500
501 # Passes w/ all real extents.
502 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
503 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700504 23,
Gilad Arnold5502b562013-03-08 13:22:31 -0800505 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700506 collections.defaultdict(int), 'foo'))
Gilad Arnold5502b562013-03-08 13:22:31 -0800507
508 # Passes w/ pseudo-extents (aka sparse holes).
509 extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5),
510 (8, 3))
511 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700512 12,
Gilad Arnold5502b562013-03-08 13:22:31 -0800513 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
514 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700515 allow_pseudo=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800516
517 # Passes w/ pseudo-extent due to a signature.
518 extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2))
519 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700520 2,
Gilad Arnold5502b562013-03-08 13:22:31 -0800521 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
522 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700523 allow_signature=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800524
525 # Fails, extent missing a start block.
526 extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16))
527 self.assertRaises(
528 update_payload.PayloadError, payload_checker._CheckExtents,
529 extents, (1024 + 16) * block_size, collections.defaultdict(int),
530 'foo')
531
532 # Fails, extent missing block count.
533 extents = self.NewExtentList((0, -1), (8, 3), (1024, 16))
534 self.assertRaises(
535 update_payload.PayloadError, payload_checker._CheckExtents,
536 extents, (1024 + 16) * block_size, collections.defaultdict(int),
537 'foo')
538
539 # Fails, extent has zero blocks.
540 extents = self.NewExtentList((0, 4), (8, 3), (1024, 0))
541 self.assertRaises(
542 update_payload.PayloadError, payload_checker._CheckExtents,
543 extents, (1024 + 16) * block_size, collections.defaultdict(int),
544 'foo')
545
546 # Fails, extent exceeds partition boundaries.
547 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
548 self.assertRaises(
549 update_payload.PayloadError, payload_checker._CheckExtents,
550 extents, (1024 + 15) * block_size, collections.defaultdict(int),
551 'foo')
552
553 def testCheckReplaceOperation(self):
554 """Tests _CheckReplaceOperation() where op.type == REPLACE."""
555 payload_checker = checker.PayloadChecker(self.MockPayload())
556 block_size = payload_checker.block_size
557 data_length = 10000
558
559 op = self.mox.CreateMock(
560 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
561 op.type = common.OpType.REPLACE
562
563 # Pass.
564 op.src_extents = []
565 self.assertIsNone(
566 payload_checker._CheckReplaceOperation(
567 op, data_length, (data_length + block_size - 1) / block_size,
568 'foo'))
569
570 # Fail, src extents founds.
571 op.src_extents = ['bar']
572 self.assertRaises(
573 update_payload.PayloadError,
574 payload_checker._CheckReplaceOperation,
575 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
576
577 # Fail, missing data.
578 op.src_extents = []
579 self.assertRaises(
580 update_payload.PayloadError,
581 payload_checker._CheckReplaceOperation,
582 op, None, (data_length + block_size - 1) / block_size, 'foo')
583
584 # Fail, length / block number mismatch.
585 op.src_extents = ['bar']
586 self.assertRaises(
587 update_payload.PayloadError,
588 payload_checker._CheckReplaceOperation,
589 op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo')
590
591 def testCheckReplaceBzOperation(self):
592 """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ."""
593 payload_checker = checker.PayloadChecker(self.MockPayload())
594 block_size = payload_checker.block_size
595 data_length = block_size * 3
596
597 op = self.mox.CreateMock(
598 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
599 op.type = common.OpType.REPLACE_BZ
600
601 # Pass.
602 op.src_extents = []
603 self.assertIsNone(
604 payload_checker._CheckReplaceOperation(
605 op, data_length, (data_length + block_size - 1) / block_size + 5,
606 'foo'))
607
608 # Fail, src extents founds.
609 op.src_extents = ['bar']
610 self.assertRaises(
611 update_payload.PayloadError,
612 payload_checker._CheckReplaceOperation,
613 op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
614
615 # Fail, missing data.
616 op.src_extents = []
617 self.assertRaises(
618 update_payload.PayloadError,
619 payload_checker._CheckReplaceOperation,
620 op, None, (data_length + block_size - 1) / block_size, 'foo')
621
622 # Fail, too few blocks to justify BZ.
623 op.src_extents = []
624 self.assertRaises(
625 update_payload.PayloadError,
626 payload_checker._CheckReplaceOperation,
627 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
628
629 def testCheckMoveOperation_Pass(self):
630 """Tests _CheckMoveOperation(); pass case."""
631 payload_checker = checker.PayloadChecker(self.MockPayload())
632 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
633 op.type = common.OpType.MOVE
634
635 self.AddToMessage(op.src_extents,
636 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
637 self.AddToMessage(op.dst_extents,
638 self.NewExtentList((16, 128), (512, 6)))
639 self.assertIsNone(
640 payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo'))
641
642 def testCheckMoveOperation_FailContainsData(self):
643 """Tests _CheckMoveOperation(); fails, message contains data."""
644 payload_checker = checker.PayloadChecker(self.MockPayload())
645 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
646 op.type = common.OpType.MOVE
647
648 self.AddToMessage(op.src_extents,
649 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
650 self.AddToMessage(op.dst_extents,
651 self.NewExtentList((16, 128), (512, 6)))
652 self.assertRaises(
653 update_payload.PayloadError,
654 payload_checker._CheckMoveOperation,
655 op, 1024, 134, 134, 'foo')
656
657 def testCheckMoveOperation_FailInsufficientSrcBlocks(self):
658 """Tests _CheckMoveOperation(); fails, not enough actual src blocks."""
659 payload_checker = checker.PayloadChecker(self.MockPayload())
660 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
661 op.type = common.OpType.MOVE
662
663 self.AddToMessage(op.src_extents,
664 self.NewExtentList((0, 4), (12, 2), (1024, 127)))
665 self.AddToMessage(op.dst_extents,
666 self.NewExtentList((16, 128), (512, 6)))
667 self.assertRaises(
668 update_payload.PayloadError,
669 payload_checker._CheckMoveOperation,
670 op, None, 134, 134, 'foo')
671
672 def testCheckMoveOperation_FailInsufficientDstBlocks(self):
673 """Tests _CheckMoveOperation(); fails, not enough actual dst blocks."""
674 payload_checker = checker.PayloadChecker(self.MockPayload())
675 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
676 op.type = common.OpType.MOVE
677
678 self.AddToMessage(op.src_extents,
679 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
680 self.AddToMessage(op.dst_extents,
681 self.NewExtentList((16, 128), (512, 5)))
682 self.assertRaises(
683 update_payload.PayloadError,
684 payload_checker._CheckMoveOperation,
685 op, None, 134, 134, 'foo')
686
687 def testCheckMoveOperation_FailExcessSrcBlocks(self):
688 """Tests _CheckMoveOperation(); fails, too many actual src blocks."""
689 payload_checker = checker.PayloadChecker(self.MockPayload())
690 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
691 op.type = common.OpType.MOVE
692
693 self.AddToMessage(op.src_extents,
694 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
695 self.AddToMessage(op.dst_extents,
696 self.NewExtentList((16, 128), (512, 5)))
697 self.assertRaises(
698 update_payload.PayloadError,
699 payload_checker._CheckMoveOperation,
700 op, None, 134, 134, 'foo')
701 self.AddToMessage(op.src_extents,
702 self.NewExtentList((0, 4), (12, 2), (1024, 129)))
703 self.AddToMessage(op.dst_extents,
704 self.NewExtentList((16, 128), (512, 6)))
705 self.assertRaises(
706 update_payload.PayloadError,
707 payload_checker._CheckMoveOperation,
708 op, None, 134, 134, 'foo')
709
710 def testCheckMoveOperation_FailExcessDstBlocks(self):
711 """Tests _CheckMoveOperation(); fails, too many actual dst blocks."""
712 payload_checker = checker.PayloadChecker(self.MockPayload())
713 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
714 op.type = common.OpType.MOVE
715
716 self.AddToMessage(op.src_extents,
717 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
718 self.AddToMessage(op.dst_extents,
719 self.NewExtentList((16, 128), (512, 7)))
720 self.assertRaises(
721 update_payload.PayloadError,
722 payload_checker._CheckMoveOperation,
723 op, None, 134, 134, 'foo')
724
725 def testCheckMoveOperation_FailStagnantBlocks(self):
726 """Tests _CheckMoveOperation(); fails, there are blocks that do not move."""
727 payload_checker = checker.PayloadChecker(self.MockPayload())
728 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
729 op.type = common.OpType.MOVE
730
731 self.AddToMessage(op.src_extents,
732 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
733 self.AddToMessage(op.dst_extents,
734 self.NewExtentList((8, 128), (512, 6)))
735 self.assertRaises(
736 update_payload.PayloadError,
737 payload_checker._CheckMoveOperation,
738 op, None, 134, 134, 'foo')
739
740 def testCheckBsdiff(self):
741 """Tests _CheckMoveOperation()."""
742 payload_checker = checker.PayloadChecker(self.MockPayload())
743
744 # Pass.
745 self.assertIsNone(
746 payload_checker._CheckBsdiffOperation(10000, 3, 'foo'))
747
748 # Fail, missing data blob.
749 self.assertRaises(
750 update_payload.PayloadError,
751 payload_checker._CheckBsdiffOperation,
752 None, 3, 'foo')
753
754 # Fail, too big of a diff blob (unjustified).
755 self.assertRaises(
756 update_payload.PayloadError,
757 payload_checker._CheckBsdiffOperation,
758 10000, 2, 'foo')
759
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800760 def testCheckSourceCopyOperation_Pass(self):
761 """Tests _CheckSourceCopyOperation(); pass case."""
762 payload_checker = checker.PayloadChecker(self.MockPayload())
763 self.assertIsNone(
764 payload_checker._CheckSourceCopyOperation(None, 134, 134, 'foo'))
765
766 def testCheckSourceCopyOperation_FailContainsData(self):
767 """Tests _CheckSourceCopyOperation(); message contains data."""
768 payload_checker = checker.PayloadChecker(self.MockPayload())
769 self.assertRaises(update_payload.PayloadError,
770 payload_checker._CheckSourceCopyOperation,
771 134, 0, 0, 'foo')
772
773 def testCheckSourceCopyOperation_FailBlockCountsMismatch(self):
774 """Tests _CheckSourceCopyOperation(); src and dst block totals not equal."""
775 payload_checker = checker.PayloadChecker(self.MockPayload())
776 self.assertRaises(update_payload.PayloadError,
777 payload_checker._CheckSourceCopyOperation,
778 None, 0, 1, 'foo')
779
Gilad Arnold5502b562013-03-08 13:22:31 -0800780 def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
781 allow_unhashed, fail_src_extents, fail_dst_extents,
782 fail_mismatched_data_offset_length,
783 fail_missing_dst_extents, fail_src_length,
784 fail_dst_length, fail_data_hash,
785 fail_prev_data_offset):
786 """Parametric testing of _CheckOperation().
787
788 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800789 op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE', 'BSDIFF', 'SOURCE_COPY',
790 or 'SOURCE_BSDIFF'.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700791 is_last: Whether we're testing the last operation in a sequence.
792 allow_signature: Whether we're testing a signature-capable operation.
793 allow_unhashed: Whether we're allowing to not hash the data.
794 fail_src_extents: Tamper with src extents.
795 fail_dst_extents: Tamper with dst extents.
796 fail_mismatched_data_offset_length: Make data_{offset,length}
797 inconsistent.
798 fail_missing_dst_extents: Do not include dst extents.
799 fail_src_length: Make src length inconsistent.
800 fail_dst_length: Make dst length inconsistent.
801 fail_data_hash: Tamper with the data blob hash.
802 fail_prev_data_offset: Make data space uses incontiguous.
Gilad Arnold5502b562013-03-08 13:22:31 -0800803 """
804 op_type = _OpTypeByName(op_type_name)
805
806 # Create the test object.
807 payload = self.MockPayload()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700808 payload_checker = checker.PayloadChecker(payload,
809 allow_unhashed=allow_unhashed)
Gilad Arnold5502b562013-03-08 13:22:31 -0800810 block_size = payload_checker.block_size
811
812 # Create auxiliary arguments.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700813 old_part_size = test_utils.MiB(4)
814 new_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800815 old_block_counters = array.array(
816 'B', [0] * ((old_part_size + block_size - 1) / block_size))
817 new_block_counters = array.array(
818 'B', [0] * ((new_part_size + block_size - 1) / block_size))
819 prev_data_offset = 1876
820 blob_hash_counts = collections.defaultdict(int)
821
822 # Create the operation object for the test.
823 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
824 op.type = op_type
825
826 total_src_blocks = 0
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800827 if op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
828 common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
Gilad Arnold5502b562013-03-08 13:22:31 -0800829 if fail_src_extents:
830 self.AddToMessage(op.src_extents,
831 self.NewExtentList((0, 0)))
832 else:
833 self.AddToMessage(op.src_extents,
834 self.NewExtentList((0, 16)))
835 total_src_blocks = 16
836
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800837 if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
Gilad Arnold5502b562013-03-08 13:22:31 -0800838 if not fail_mismatched_data_offset_length:
839 op.data_length = 16 * block_size - 8
840 if fail_prev_data_offset:
841 op.data_offset = prev_data_offset + 16
842 else:
843 op.data_offset = prev_data_offset
844
845 fake_data = 'fake-data'.ljust(op.data_length)
846 if not (allow_unhashed or (is_last and allow_signature and
847 op_type == common.OpType.REPLACE)):
848 if not fail_data_hash:
849 # Create a valid data blob hash.
850 op.data_sha256_hash = hashlib.sha256(fake_data).digest()
851 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
852 fake_data)
853 elif fail_data_hash:
854 # Create an invalid data blob hash.
855 op.data_sha256_hash = hashlib.sha256(
856 fake_data.replace(' ', '-')).digest()
857 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
858 fake_data)
859
860 total_dst_blocks = 0
861 if not fail_missing_dst_extents:
862 total_dst_blocks = 16
863 if fail_dst_extents:
864 self.AddToMessage(op.dst_extents,
865 self.NewExtentList((4, 16), (32, 0)))
866 else:
867 self.AddToMessage(op.dst_extents,
868 self.NewExtentList((4, 8), (64, 8)))
869
870 if total_src_blocks:
871 if fail_src_length:
872 op.src_length = total_src_blocks * block_size + 8
873 else:
874 op.src_length = total_src_blocks * block_size
875 elif fail_src_length:
876 # Add an orphaned src_length.
877 op.src_length = 16
878
879 if total_dst_blocks:
880 if fail_dst_length:
881 op.dst_length = total_dst_blocks * block_size + 8
882 else:
883 op.dst_length = total_dst_blocks * block_size
884
885 self.mox.ReplayAll()
886 should_fail = (fail_src_extents or fail_dst_extents or
887 fail_mismatched_data_offset_length or
888 fail_missing_dst_extents or fail_src_length or
889 fail_dst_length or fail_data_hash or fail_prev_data_offset)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700890 args = (op, 'foo', is_last, old_block_counters, new_block_counters,
891 old_part_size, new_part_size, prev_data_offset, allow_signature,
892 blob_hash_counts)
Gilad Arnold5502b562013-03-08 13:22:31 -0800893 if should_fail:
894 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700895 payload_checker._CheckOperation, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800896 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700897 self.assertEqual(op.data_length if op.HasField('data_length') else 0,
898 payload_checker._CheckOperation(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800899
900 def testAllocBlockCounters(self):
901 """Tests _CheckMoveOperation()."""
902 payload_checker = checker.PayloadChecker(self.MockPayload())
903 block_size = payload_checker.block_size
904
905 # Check allocation for block-aligned partition size, ensure it's integers.
906 result = payload_checker._AllocBlockCounters(16 * block_size)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700907 self.assertEqual(16, len(result))
908 self.assertEqual(int, type(result[0]))
Gilad Arnold5502b562013-03-08 13:22:31 -0800909
910 # Check allocation of unaligned partition sizes.
911 result = payload_checker._AllocBlockCounters(16 * block_size - 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700912 self.assertEqual(16, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800913 result = payload_checker._AllocBlockCounters(16 * block_size + 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700914 self.assertEqual(17, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800915
916 def DoCheckOperationsTest(self, fail_bad_type,
917 fail_nonexhaustive_full_update):
918 # Generate a test payload. For this test, we only care about one
919 # (arbitrary) set of operations, so we'll only be generating kernel and
920 # test with them.
921 payload_gen = test_utils.PayloadGenerator()
922
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700923 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800924 payload_gen.SetBlockSize(block_size)
925
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700926 rootfs_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800927
928 # Fake rootfs operations in a full update, tampered with as required.
929 rootfs_op_type = common.OpType.REPLACE
930 if fail_bad_type:
931 # Choose a type value that's bigger than the highest valid value.
932 for valid_op_type in common.OpType.ALL:
933 rootfs_op_type = max(rootfs_op_type, valid_op_type)
934 rootfs_op_type += 1
935
936 rootfs_data_length = rootfs_part_size
937 if fail_nonexhaustive_full_update:
938 rootfs_data_length -= block_size
939
940 payload_gen.AddOperation(False, rootfs_op_type,
941 dst_extents=[(0, rootfs_data_length / block_size)],
942 data_offset=0,
943 data_length=rootfs_data_length)
944
945 # Create the test object.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700946 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
947 checker_init_dargs={
948 'allow_unhashed': True})
Gilad Arnold5502b562013-03-08 13:22:31 -0800949 payload_checker.payload_type = checker._TYPE_FULL
950 report = checker._PayloadReport()
951
952 should_fail = (fail_bad_type or fail_nonexhaustive_full_update)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700953 args = (payload_checker.payload.manifest.install_operations, report,
954 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
Gilad Arnold5502b562013-03-08 13:22:31 -0800955 if should_fail:
956 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700957 payload_checker._CheckOperations, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800958 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700959 self.assertEqual(rootfs_data_length,
960 payload_checker._CheckOperations(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800961
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800962 def testCheckOperationsMinorVersion(self):
963 """Tests _CheckOperations; checks op compatibility with minor version.
964
965 SOURCE_COPY and SOURCE_BSDIFF operations are not supported in payloads
966 with delta minor version 1. MOVE and BSDIFF operations are not supported
967 in payloads with delta minor version 2.
968 """
969 # Create test objects and parameters.
970 payload_checker = checker.PayloadChecker(self.MockPayload())
971 report = checker._PayloadReport()
972 rootfs_part_size = test_utils.MiB(8)
973
974 FakeOp = collections.namedtuple('FakeOp', ['type'])
975 args = ([FakeOp(common.OpType.SOURCE_COPY),
976 FakeOp(common.OpType.SOURCE_BSDIFF)],
977 report, 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
978
979 try:
980 # Mock _CheckOperation() so it will pass.
981 self.mox.StubOutWithMock(payload_checker, '_CheckOperation')
982 payload_checker._CheckOperation(mox.IgnoreArg(), mox.IgnoreArg(),
983 mox.IgnoreArg(), mox.IgnoreArg(),
984 mox.IgnoreArg(), mox.IgnoreArg(),
985 mox.IgnoreArg(), mox.IgnoreArg(),
986 mox.IgnoreArg(), mox.IgnoreArg()
987 ).MultipleTimes().AndReturn(0)
988 self.mox.ReplayAll()
989
990 # Fail, minor version 1 should not allow SOURCE_COPY or SOURCE_BSDIFF.
991 payload_checker.payload.manifest.minor_version = 1
992 self.assertRaises(update_payload.PayloadError,
993 payload_checker._CheckOperations,
994 *args)
995
996 # Pass, minor version 2 should allow these operations.
997 payload_checker.payload.manifest.minor_version = 2
998 self.assertEquals(payload_checker._CheckOperations(*args), 0)
999
1000 # Fail, minor version 2 should not allow MOVE or BSDIFF.
1001 args = ([FakeOp(common.OpType.MOVE), FakeOp(common.OpType.BSDIFF)],
1002 report, 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
1003 self.assertRaises(update_payload.PayloadError,
1004 payload_checker._CheckOperations,
1005 *args)
1006
1007 finally:
1008 self.mox.UnsetStubs()
1009
Gilad Arnold5502b562013-03-08 13:22:31 -08001010 def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
1011 fail_mismatched_pseudo_op, fail_sig_missing_fields,
1012 fail_unknown_sig_version, fail_incorrect_sig):
1013 # Generate a test payload. For this test, we only care about the signature
1014 # block and how it relates to the payload hash. Therefore, we're generating
1015 # a random (otherwise useless) payload for this purpose.
1016 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001017 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001018 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001019 rootfs_part_size = test_utils.MiB(2)
1020 kernel_part_size = test_utils.KiB(16)
Gilad Arnold5502b562013-03-08 13:22:31 -08001021 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1022 hashlib.sha256('fake-new-rootfs-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -07001023 payload_gen.SetPartInfo(True, True, kernel_part_size,
Gilad Arnold5502b562013-03-08 13:22:31 -08001024 hashlib.sha256('fake-new-kernel-content').digest())
1025 payload_gen.AddOperationWithData(
1026 False, common.OpType.REPLACE,
1027 dst_extents=[(0, rootfs_part_size / block_size)],
1028 data_blob=os.urandom(rootfs_part_size))
1029
1030 do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
1031 do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
1032 fail_sig_missing_fields or fail_unknown_sig_version
1033 or fail_incorrect_sig)
1034
1035 sigs_data = None
1036 if do_forge_sigs_data:
1037 sigs_gen = test_utils.SignaturesGenerator()
1038 if not fail_empty_sigs_blob:
1039 if fail_sig_missing_fields:
1040 sig_data = None
1041 else:
1042 sig_data = test_utils.SignSha256('fake-payload-content',
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001043 test_utils._PRIVKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001044 sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
1045
1046 sigs_data = sigs_gen.ToBinary()
1047 payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
1048
1049 if do_forge_pseudo_op:
1050 assert sigs_data is not None, 'should have forged signatures blob by now'
1051 sigs_len = len(sigs_data)
1052 payload_gen.AddOperation(
1053 False, common.OpType.REPLACE,
1054 data_offset=payload_gen.curr_offset / 2,
1055 data_length=sigs_len / 2,
1056 dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
1057
1058 # Generate payload (complete w/ signature) and create the test object.
1059 payload_checker = _GetPayloadChecker(
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001060 payload_gen.WriteToFileWithData,
1061 payload_gen_dargs={
1062 'sigs_data': sigs_data,
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001063 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001064 'do_add_pseudo_operation': not do_forge_pseudo_op})
Gilad Arnold5502b562013-03-08 13:22:31 -08001065 payload_checker.payload_type = checker._TYPE_FULL
1066 report = checker._PayloadReport()
1067
1068 # We have to check the manifest first in order to set signature attributes.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001069 payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -08001070
1071 should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
1072 fail_mismatched_pseudo_op or fail_sig_missing_fields or
1073 fail_unknown_sig_version or fail_incorrect_sig)
Gilad Arnoldcb638912013-06-24 04:57:11 -07001074 args = (report, test_utils._PUBKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001075 if should_fail:
1076 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001077 payload_checker._CheckSignatures, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -08001078 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001079 self.assertIsNone(payload_checker._CheckSignatures(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -08001080
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001081 def DoCheckMinorVersionTest(self, minor_version, payload_type):
1082 """Parametric testing for CheckMinorVersion().
1083
1084 Args:
1085 minor_version: The payload minor version to test with.
1086 payload_type: The type of the payload we're testing, delta or full.
1087 """
1088 # Create the test object.
1089 payload_checker = checker.PayloadChecker(self.MockPayload())
1090 report = checker._PayloadReport()
1091
1092 should_succeed = (
1093 (minor_version == 0 and payload_type == checker._TYPE_FULL) or
1094 (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
1095 (minor_version == 2 and payload_type == checker._TYPE_DELTA))
1096 args = (report, minor_version, payload_type)
1097
1098 if should_succeed:
1099 self.assertIsNone(payload_checker._CheckMinorVersion(*args))
1100 else:
1101 self.assertRaises(update_payload.PayloadError,
1102 payload_checker._CheckMinorVersion, *args)
1103
Gilad Arnold5502b562013-03-08 13:22:31 -08001104 def DoRunTest(self, fail_wrong_payload_type, fail_invalid_block_size,
1105 fail_mismatched_block_size, fail_excess_data):
1106 # Generate a test payload. For this test, we generate a full update that
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001107 # has sample kernel and rootfs operations. Since most testing is done with
Gilad Arnold5502b562013-03-08 13:22:31 -08001108 # internal PayloadChecker methods that are tested elsewhere, here we only
1109 # tamper with what's actually being manipulated and/or tested in the Run()
1110 # method itself. Note that the checker doesn't verify partition hashes, so
1111 # they're safe to fake.
1112 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001113 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001114 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001115 kernel_part_size = test_utils.KiB(16)
1116 rootfs_part_size = test_utils.MiB(2)
Gilad Arnold5502b562013-03-08 13:22:31 -08001117 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1118 hashlib.sha256('fake-new-rootfs-content').digest())
1119 payload_gen.SetPartInfo(True, True, kernel_part_size,
1120 hashlib.sha256('fake-new-kernel-content').digest())
1121 payload_gen.AddOperationWithData(
1122 False, common.OpType.REPLACE,
1123 dst_extents=[(0, rootfs_part_size / block_size)],
1124 data_blob=os.urandom(rootfs_part_size))
1125 payload_gen.AddOperationWithData(
1126 True, common.OpType.REPLACE,
1127 dst_extents=[(0, kernel_part_size / block_size)],
1128 data_blob=os.urandom(kernel_part_size))
1129
1130 # Generate payload (complete w/ signature) and create the test object.
Gilad Arnold5502b562013-03-08 13:22:31 -08001131 if fail_invalid_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001132 use_block_size = block_size + 5 # Not a power of two.
Gilad Arnold5502b562013-03-08 13:22:31 -08001133 elif fail_mismatched_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001134 use_block_size = block_size * 2 # Different that payload stated.
Gilad Arnold5502b562013-03-08 13:22:31 -08001135 else:
1136 use_block_size = block_size
Gilad Arnold5502b562013-03-08 13:22:31 -08001137
Gilad Arnoldcb638912013-06-24 04:57:11 -07001138 kwargs = {
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001139 'payload_gen_dargs': {
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001140 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001141 'do_add_pseudo_operation': True,
1142 'is_pseudo_in_kernel': True,
1143 'padding': os.urandom(1024) if fail_excess_data else None},
1144 'checker_init_dargs': {
1145 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
1146 'block_size': use_block_size}}
1147 if fail_invalid_block_size:
1148 self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001149 payload_gen.WriteToFileWithData, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -08001150 else:
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001151 payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001152 **kwargs)
1153 kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME}
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001154 should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
1155 fail_excess_data)
1156 if should_fail:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001157 self.assertRaises(update_payload.PayloadError, payload_checker.Run,
1158 **kwargs)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001159 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001160 self.assertIsNone(payload_checker.Run(**kwargs))
Gilad Arnold5502b562013-03-08 13:22:31 -08001161
Gilad Arnold5502b562013-03-08 13:22:31 -08001162# This implements a generic API, hence the occasional unused args.
1163# pylint: disable=W0613
1164def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
1165 allow_unhashed, fail_src_extents,
1166 fail_dst_extents,
1167 fail_mismatched_data_offset_length,
1168 fail_missing_dst_extents, fail_src_length,
1169 fail_dst_length, fail_data_hash,
1170 fail_prev_data_offset):
1171 """Returns True iff the combination of arguments represents a valid test."""
1172 op_type = _OpTypeByName(op_type_name)
1173
1174 # REPLACE/REPLACE_BZ operations don't read data from src partition.
1175 if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
1176 fail_src_extents or fail_src_length)):
1177 return False
1178
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001179 # MOVE and SOURCE_COPY operations don't carry data.
1180 if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and (
Gilad Arnold5502b562013-03-08 13:22:31 -08001181 fail_mismatched_data_offset_length or fail_data_hash or
1182 fail_prev_data_offset)):
1183 return False
1184
1185 return True
1186
1187
1188def TestMethodBody(run_method_name, run_dargs):
1189 """Returns a function that invokes a named method with named arguments."""
1190 return lambda self: getattr(self, run_method_name)(**run_dargs)
1191
1192
1193def AddParametricTests(tested_method_name, arg_space, validate_func=None):
1194 """Enumerates and adds specific parametric tests to PayloadCheckerTest.
1195
1196 This function enumerates a space of test parameters (defined by arg_space),
1197 then binds a new, unique method name in PayloadCheckerTest to a test function
1198 that gets handed the said parameters. This is a preferable approach to doing
1199 the enumeration and invocation during the tests because this way each test is
1200 treated as a complete run by the unittest framework, and so benefits from the
1201 usual setUp/tearDown mechanics.
1202
1203 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001204 tested_method_name: Name of the tested PayloadChecker method.
1205 arg_space: A dictionary containing variables (keys) and lists of values
1206 (values) associated with them.
1207 validate_func: A function used for validating test argument combinations.
Gilad Arnold5502b562013-03-08 13:22:31 -08001208 """
1209 for value_tuple in itertools.product(*arg_space.itervalues()):
1210 run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
1211 if validate_func and not validate_func(**run_dargs):
1212 continue
1213 run_method_name = 'Do%sTest' % tested_method_name
1214 test_method_name = 'test%s' % tested_method_name
1215 for arg_key, arg_val in run_dargs.iteritems():
1216 if arg_val or type(arg_val) is int:
1217 test_method_name += '__%s=%s' % (arg_key, arg_val)
1218 setattr(PayloadCheckerTest, test_method_name,
1219 TestMethodBody(run_method_name, run_dargs))
1220
1221
1222def AddAllParametricTests():
1223 """Enumerates and adds all parametric tests to PayloadCheckerTest."""
1224 # Add all _CheckElem() test cases.
1225 AddParametricTests('AddElem',
1226 {'linebreak': (True, False),
1227 'indent': (0, 1, 2),
1228 'convert': (str, lambda s: s[::-1]),
1229 'is_present': (True, False),
1230 'is_mandatory': (True, False),
1231 'is_submsg': (True, False)})
1232
1233 # Add all _Add{Mandatory,Optional}Field tests.
1234 AddParametricTests('AddField',
1235 {'is_mandatory': (True, False),
1236 'linebreak': (True, False),
1237 'indent': (0, 1, 2),
1238 'convert': (str, lambda s: s[::-1]),
1239 'is_present': (True, False)})
1240
1241 # Add all _Add{Mandatory,Optional}SubMsg tests.
1242 AddParametricTests('AddSubMsg',
1243 {'is_mandatory': (True, False),
1244 'is_present': (True, False)})
1245
1246 # Add all _CheckManifest() test cases.
1247 AddParametricTests('CheckManifest',
1248 {'fail_mismatched_block_size': (True, False),
1249 'fail_bad_sigs': (True, False),
1250 'fail_mismatched_oki_ori': (True, False),
1251 'fail_bad_oki': (True, False),
1252 'fail_bad_ori': (True, False),
1253 'fail_bad_nki': (True, False),
1254 'fail_bad_nri': (True, False),
Gilad Arnold382df5c2013-05-03 12:49:28 -07001255 'fail_old_kernel_fs_size': (True, False),
1256 'fail_old_rootfs_fs_size': (True, False),
1257 'fail_new_kernel_fs_size': (True, False),
1258 'fail_new_rootfs_fs_size': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001259
1260 # Add all _CheckOperation() test cases.
1261 AddParametricTests('CheckOperation',
1262 {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001263 'BSDIFF', 'SOURCE_COPY',
1264 'SOURCE_BSDIFF'),
Gilad Arnold5502b562013-03-08 13:22:31 -08001265 'is_last': (True, False),
1266 'allow_signature': (True, False),
1267 'allow_unhashed': (True, False),
1268 'fail_src_extents': (True, False),
1269 'fail_dst_extents': (True, False),
1270 'fail_mismatched_data_offset_length': (True, False),
1271 'fail_missing_dst_extents': (True, False),
1272 'fail_src_length': (True, False),
1273 'fail_dst_length': (True, False),
1274 'fail_data_hash': (True, False),
1275 'fail_prev_data_offset': (True, False)},
1276 validate_func=ValidateCheckOperationTest)
1277
1278 # Add all _CheckOperations() test cases.
1279 AddParametricTests('CheckOperations',
1280 {'fail_bad_type': (True, False),
1281 'fail_nonexhaustive_full_update': (True, False)})
1282
1283 # Add all _CheckOperations() test cases.
1284 AddParametricTests('CheckSignatures',
1285 {'fail_empty_sigs_blob': (True, False),
1286 'fail_missing_pseudo_op': (True, False),
1287 'fail_mismatched_pseudo_op': (True, False),
1288 'fail_sig_missing_fields': (True, False),
1289 'fail_unknown_sig_version': (True, False),
1290 'fail_incorrect_sig': (True, False)})
1291
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001292 # Add all _CheckMinorVersion() test cases.
1293 AddParametricTests('CheckMinorVersion',
1294 {'minor_version': (0, 1, 2, 555),
1295 'payload_type': (checker._TYPE_FULL,
1296 checker._TYPE_DELTA)})
1297
Gilad Arnold5502b562013-03-08 13:22:31 -08001298 # Add all Run() test cases.
1299 AddParametricTests('Run',
1300 {'fail_wrong_payload_type': (True, False),
1301 'fail_invalid_block_size': (True, False),
1302 'fail_mismatched_block_size': (True, False),
1303 'fail_excess_data': (True, False)})
1304
1305
1306if __name__ == '__main__':
1307 AddAllParametricTests()
1308 unittest.main()