blob: c099e2a4cc9f6ffc9d38ca8864cb07cda40e6eea [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,
36 }
37 return op_name_to_type[op_name]
38
39
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070040def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None,
41 checker_init_dargs=None):
Gilad Arnold5502b562013-03-08 13:22:31 -080042 """Returns a payload checker from a given payload generator."""
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070043 if payload_gen_dargs is None:
44 payload_gen_dargs = {}
45 if checker_init_dargs is None:
46 checker_init_dargs = {}
47
Gilad Arnold5502b562013-03-08 13:22:31 -080048 payload_file = cStringIO.StringIO()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070049 payload_gen_write_to_file_func(payload_file, **payload_gen_dargs)
Gilad Arnold5502b562013-03-08 13:22:31 -080050 payload_file.seek(0)
51 payload = update_payload.Payload(payload_file)
52 payload.Init()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -070053 return checker.PayloadChecker(payload, **checker_init_dargs)
Gilad Arnold5502b562013-03-08 13:22:31 -080054
55
56def _GetPayloadCheckerWithData(payload_gen):
57 """Returns a payload checker from a given payload generator."""
58 payload_file = cStringIO.StringIO()
59 payload_gen.WriteToFile(payload_file)
60 payload_file.seek(0)
61 payload = update_payload.Payload(payload_file)
62 payload.Init()
63 return checker.PayloadChecker(payload)
64
65
Gilad Arnoldcb638912013-06-24 04:57:11 -070066# This class doesn't need an __init__().
Gilad Arnold5502b562013-03-08 13:22:31 -080067# pylint: disable=W0232
Gilad Arnoldcb638912013-06-24 04:57:11 -070068# Unit testing is all about running protected methods.
Gilad Arnold5502b562013-03-08 13:22:31 -080069# pylint: disable=W0212
Gilad Arnoldcb638912013-06-24 04:57:11 -070070# Don't bark about missing members of classes you cannot import.
Gilad Arnold5502b562013-03-08 13:22:31 -080071# pylint: disable=E1101
72class PayloadCheckerTest(mox.MoxTestBase):
73 """Tests the PayloadChecker class.
74
75 In addition to ordinary testFoo() methods, which are automatically invoked by
76 the unittest framework, in this class we make use of DoBarTest() calls that
77 implement parametric tests of certain features. In order to invoke each test,
78 which embodies a unique combination of parameter values, as a complete unit
79 test, we perform explicit enumeration of the parameter space and create
80 individual invocation contexts for each, which are then bound as
81 testBar__param1=val1__param2=val2(). The enumeration of parameter spaces for
82 all such tests is done in AddAllParametricTests().
Gilad Arnold5502b562013-03-08 13:22:31 -080083 """
84
85 def MockPayload(self):
86 """Create a mock payload object, complete with a mock menifest."""
87 payload = self.mox.CreateMock(update_payload.Payload)
88 payload.is_init = True
89 payload.manifest = self.mox.CreateMock(
90 update_metadata_pb2.DeltaArchiveManifest)
91 return payload
92
93 @staticmethod
94 def NewExtent(start_block, num_blocks):
95 """Returns an Extent message.
96
97 Each of the provided fields is set iff it is >= 0; otherwise, it's left at
98 its default state.
99
100 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700101 start_block: The starting block of the extent.
102 num_blocks: The number of blocks in the extent.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800103
Gilad Arnold5502b562013-03-08 13:22:31 -0800104 Returns:
105 An Extent message.
Gilad Arnold5502b562013-03-08 13:22:31 -0800106 """
107 ex = update_metadata_pb2.Extent()
108 if start_block >= 0:
109 ex.start_block = start_block
110 if num_blocks >= 0:
111 ex.num_blocks = num_blocks
112 return ex
113
114 @staticmethod
115 def NewExtentList(*args):
116 """Returns an list of extents.
117
118 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700119 *args: (start_block, num_blocks) pairs defining the extents.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800120
Gilad Arnold5502b562013-03-08 13:22:31 -0800121 Returns:
122 A list of Extent objects.
Gilad Arnold5502b562013-03-08 13:22:31 -0800123 """
124 ex_list = []
125 for start_block, num_blocks in args:
126 ex_list.append(PayloadCheckerTest.NewExtent(start_block, num_blocks))
127 return ex_list
128
129 @staticmethod
130 def AddToMessage(repeated_field, field_vals):
131 for field_val in field_vals:
132 new_field = repeated_field.add()
133 new_field.CopyFrom(field_val)
134
Gilad Arnoldcb638912013-06-24 04:57:11 -0700135 # The production environment uses an older Python, this isn't an override.
136 # pylint: disable=W0221
Gilad Arnold5502b562013-03-08 13:22:31 -0800137 def assertIsNone(self, val):
138 """Asserts that val is None (TODO remove once we upgrade to Python 2.7).
139
140 Note that we're using assertEqual so as for it to show us the actual
141 non-None value.
142
143 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700144 val: Value/object to be equated to None.
Gilad Arnold5502b562013-03-08 13:22:31 -0800145 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700146 self.assertIs(None, val)
Gilad Arnold5502b562013-03-08 13:22:31 -0800147
148 def SetupAddElemTest(self, is_present, is_submsg, convert=str,
149 linebreak=False, indent=0):
150 """Setup for testing of _CheckElem() and its derivatives.
151
152 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700153 is_present: Whether or not the element is found in the message.
154 is_submsg: Whether the element is a sub-message itself.
155 convert: A representation conversion function.
156 linebreak: Whether or not a linebreak is to be used in the report.
157 indent: Indentation used for the report.
Gilad Arnoldf583a7d2015-02-05 13:23:55 -0800158
Gilad Arnold5502b562013-03-08 13:22:31 -0800159 Returns:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700160 msg: A mock message object.
161 report: A mock report object.
162 subreport: A mock sub-report object.
163 name: An element name to check.
164 val: Expected element value.
Gilad Arnold5502b562013-03-08 13:22:31 -0800165 """
166 name = 'foo'
167 val = 'fake submsg' if is_submsg else 'fake field'
168 subreport = 'fake subreport'
169
170 # Create a mock message.
171 msg = self.mox.CreateMock(update_metadata_pb2.message.Message)
172 msg.HasField(name).AndReturn(is_present)
173 setattr(msg, name, val)
174
175 # Create a mock report.
176 report = self.mox.CreateMock(checker._PayloadReport)
177 if is_present:
178 if is_submsg:
179 report.AddSubReport(name).AndReturn(subreport)
180 else:
181 report.AddField(name, convert(val), linebreak=linebreak, indent=indent)
182
183 self.mox.ReplayAll()
184 return (msg, report, subreport, name, val)
185
186 def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert,
187 linebreak, indent):
188 """Parametric testing of _CheckElem().
189
190 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700191 is_present: Whether or not the element is found in the message.
192 is_mandatory: Whether or not it's a mandatory element.
193 is_submsg: Whether the element is a sub-message itself.
194 convert: A representation conversion function.
195 linebreak: Whether or not a linebreak is to be used in the report.
196 indent: Indentation used for the report.
Gilad Arnold5502b562013-03-08 13:22:31 -0800197 """
198 msg, report, subreport, name, val = self.SetupAddElemTest(
199 is_present, is_submsg, convert, linebreak, indent)
200
Gilad Arnoldcb638912013-06-24 04:57:11 -0700201 args = (msg, name, report, is_mandatory, is_submsg)
202 kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
Gilad Arnold5502b562013-03-08 13:22:31 -0800203 if is_mandatory and not is_present:
204 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700205 checker.PayloadChecker._CheckElem, *args, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -0800206 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700207 ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args,
208 **kwargs)
209 self.assertEquals(val if is_present else None, ret_val)
210 self.assertEquals(subreport if is_present and is_submsg else None,
211 ret_subreport)
Gilad Arnold5502b562013-03-08 13:22:31 -0800212
213 def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak,
214 indent):
215 """Parametric testing of _Check{Mandatory,Optional}Field().
216
217 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700218 is_mandatory: Whether we're testing a mandatory call.
219 is_present: Whether or not the element is found in the message.
220 convert: A representation conversion function.
221 linebreak: Whether or not a linebreak is to be used in the report.
222 indent: Indentation used for the report.
Gilad Arnold5502b562013-03-08 13:22:31 -0800223 """
224 msg, report, _, name, val = self.SetupAddElemTest(
225 is_present, False, convert, linebreak, indent)
226
227 # Prepare for invocation of the tested method.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700228 args = [msg, name, report]
229 kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
Gilad Arnold5502b562013-03-08 13:22:31 -0800230 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700231 args.append('bar')
Gilad Arnold5502b562013-03-08 13:22:31 -0800232 tested_func = checker.PayloadChecker._CheckMandatoryField
233 else:
234 tested_func = checker.PayloadChecker._CheckOptionalField
235
236 # Test the method call.
237 if is_mandatory and not is_present:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700238 self.assertRaises(update_payload.PayloadError, tested_func, *args,
239 **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -0800240 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700241 ret_val = tested_func(*args, **kwargs)
242 self.assertEquals(val if is_present else None, ret_val)
Gilad Arnold5502b562013-03-08 13:22:31 -0800243
244 def DoAddSubMsgTest(self, is_mandatory, is_present):
245 """Parametrized testing of _Check{Mandatory,Optional}SubMsg().
246
247 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700248 is_mandatory: Whether we're testing a mandatory call.
249 is_present: Whether or not the element is found in the message.
Gilad Arnold5502b562013-03-08 13:22:31 -0800250 """
251 msg, report, subreport, name, val = self.SetupAddElemTest(is_present, True)
252
253 # Prepare for invocation of the tested method.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700254 args = [msg, name, report]
Gilad Arnold5502b562013-03-08 13:22:31 -0800255 if is_mandatory:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700256 args.append('bar')
Gilad Arnold5502b562013-03-08 13:22:31 -0800257 tested_func = checker.PayloadChecker._CheckMandatorySubMsg
258 else:
259 tested_func = checker.PayloadChecker._CheckOptionalSubMsg
260
261 # Test the method call.
262 if is_mandatory and not is_present:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700263 self.assertRaises(update_payload.PayloadError, tested_func, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800264 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700265 ret_val, ret_subreport = tested_func(*args)
266 self.assertEquals(val if is_present else None, ret_val)
267 self.assertEquals(subreport if is_present else None, ret_subreport)
Gilad Arnold5502b562013-03-08 13:22:31 -0800268
269 def testCheckPresentIff(self):
270 """Tests _CheckPresentIff()."""
271 self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
272 None, None, 'foo', 'bar', 'baz'))
273 self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
274 'a', 'b', 'foo', 'bar', 'baz'))
275 self.assertRaises(update_payload.PayloadError,
276 checker.PayloadChecker._CheckPresentIff,
277 'a', None, 'foo', 'bar', 'baz')
278 self.assertRaises(update_payload.PayloadError,
279 checker.PayloadChecker._CheckPresentIff,
280 None, 'b', 'foo', 'bar', 'baz')
281
282 def DoCheckSha256SignatureTest(self, expect_pass, expect_subprocess_call,
283 sig_data, sig_asn1_header,
284 returned_signed_hash, expected_signed_hash):
285 """Parametric testing of _CheckSha256SignatureTest().
286
287 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700288 expect_pass: Whether or not it should pass.
289 expect_subprocess_call: Whether to expect the openssl call to happen.
290 sig_data: The signature raw data.
291 sig_asn1_header: The ASN1 header.
292 returned_signed_hash: The signed hash data retuned by openssl.
293 expected_signed_hash: The signed hash data to compare against.
Gilad Arnold5502b562013-03-08 13:22:31 -0800294 """
Gilad Arnoldcb638912013-06-24 04:57:11 -0700295 try:
296 # Stub out the subprocess invocation.
297 self.mox.StubOutWithMock(checker.PayloadChecker, '_Run')
298 if expect_subprocess_call:
299 checker.PayloadChecker._Run(
300 mox.IsA(list), send_data=sig_data).AndReturn(
301 (sig_asn1_header + returned_signed_hash, None))
Gilad Arnold5502b562013-03-08 13:22:31 -0800302
Gilad Arnoldcb638912013-06-24 04:57:11 -0700303 self.mox.ReplayAll()
304 if expect_pass:
305 self.assertIsNone(checker.PayloadChecker._CheckSha256Signature(
306 sig_data, 'foo', expected_signed_hash, 'bar'))
307 else:
308 self.assertRaises(update_payload.PayloadError,
309 checker.PayloadChecker._CheckSha256Signature,
310 sig_data, 'foo', expected_signed_hash, 'bar')
311 finally:
312 self.mox.UnsetStubs()
Gilad Arnold5502b562013-03-08 13:22:31 -0800313
314 def testCheckSha256Signature_Pass(self):
315 """Tests _CheckSha256Signature(); pass case."""
316 sig_data = 'fake-signature'.ljust(256)
317 signed_hash = hashlib.sha256('fake-data').digest()
318 self.DoCheckSha256SignatureTest(True, True, sig_data,
319 common.SIG_ASN1_HEADER, signed_hash,
320 signed_hash)
321
322 def testCheckSha256Signature_FailBadSignature(self):
323 """Tests _CheckSha256Signature(); fails due to malformed signature."""
Gilad Arnoldcb638912013-06-24 04:57:11 -0700324 sig_data = 'fake-signature' # Malformed (not 256 bytes in length).
Gilad Arnold5502b562013-03-08 13:22:31 -0800325 signed_hash = hashlib.sha256('fake-data').digest()
326 self.DoCheckSha256SignatureTest(False, False, sig_data,
327 common.SIG_ASN1_HEADER, signed_hash,
328 signed_hash)
329
330 def testCheckSha256Signature_FailBadOutputLength(self):
331 """Tests _CheckSha256Signature(); fails due to unexpected output length."""
332 sig_data = 'fake-signature'.ljust(256)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700333 signed_hash = 'fake-hash' # Malformed (not 32 bytes in length).
Gilad Arnold5502b562013-03-08 13:22:31 -0800334 self.DoCheckSha256SignatureTest(False, True, sig_data,
335 common.SIG_ASN1_HEADER, signed_hash,
336 signed_hash)
337
338 def testCheckSha256Signature_FailBadAsnHeader(self):
339 """Tests _CheckSha256Signature(); fails due to bad ASN1 header."""
340 sig_data = 'fake-signature'.ljust(256)
341 signed_hash = hashlib.sha256('fake-data').digest()
342 bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER))
343 self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header,
344 signed_hash, signed_hash)
345
346 def testCheckSha256Signature_FailBadHash(self):
347 """Tests _CheckSha256Signature(); fails due to bad hash returned."""
348 sig_data = 'fake-signature'.ljust(256)
349 expected_signed_hash = hashlib.sha256('fake-data').digest()
350 returned_signed_hash = hashlib.sha256('bad-fake-data').digest()
351 self.DoCheckSha256SignatureTest(False, True, sig_data,
352 common.SIG_ASN1_HEADER,
353 expected_signed_hash, returned_signed_hash)
354
355 def testCheckBlocksFitLength_Pass(self):
356 """Tests _CheckBlocksFitLength(); pass case."""
357 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
358 64, 4, 16, 'foo'))
359 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
360 60, 4, 16, 'foo'))
361 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
362 49, 4, 16, 'foo'))
363 self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
364 48, 3, 16, 'foo'))
365
366 def testCheckBlocksFitLength_TooManyBlocks(self):
367 """Tests _CheckBlocksFitLength(); fails due to excess blocks."""
368 self.assertRaises(update_payload.PayloadError,
369 checker.PayloadChecker._CheckBlocksFitLength,
370 64, 5, 16, 'foo')
371 self.assertRaises(update_payload.PayloadError,
372 checker.PayloadChecker._CheckBlocksFitLength,
373 60, 5, 16, 'foo')
374 self.assertRaises(update_payload.PayloadError,
375 checker.PayloadChecker._CheckBlocksFitLength,
376 49, 5, 16, 'foo')
377 self.assertRaises(update_payload.PayloadError,
378 checker.PayloadChecker._CheckBlocksFitLength,
379 48, 4, 16, 'foo')
380
381 def testCheckBlocksFitLength_TooFewBlocks(self):
382 """Tests _CheckBlocksFitLength(); fails due to insufficient blocks."""
383 self.assertRaises(update_payload.PayloadError,
384 checker.PayloadChecker._CheckBlocksFitLength,
385 64, 3, 16, 'foo')
386 self.assertRaises(update_payload.PayloadError,
387 checker.PayloadChecker._CheckBlocksFitLength,
388 60, 3, 16, 'foo')
389 self.assertRaises(update_payload.PayloadError,
390 checker.PayloadChecker._CheckBlocksFitLength,
391 49, 3, 16, 'foo')
392 self.assertRaises(update_payload.PayloadError,
393 checker.PayloadChecker._CheckBlocksFitLength,
394 48, 2, 16, 'foo')
395
396 def DoCheckManifestTest(self, fail_mismatched_block_size, fail_bad_sigs,
397 fail_mismatched_oki_ori, fail_bad_oki, fail_bad_ori,
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800398 fail_bad_nki, fail_bad_nri, fail_old_kernel_fs_size,
399 fail_old_rootfs_fs_size, fail_new_kernel_fs_size,
400 fail_new_rootfs_fs_size):
Gilad Arnold5502b562013-03-08 13:22:31 -0800401 """Parametric testing of _CheckManifest().
402
403 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700404 fail_mismatched_block_size: Simulate a missing block_size field.
405 fail_bad_sigs: Make signatures descriptor inconsistent.
406 fail_mismatched_oki_ori: Make old rootfs/kernel info partially present.
407 fail_bad_oki: Tamper with old kernel info.
408 fail_bad_ori: Tamper with old rootfs info.
409 fail_bad_nki: Tamper with new kernel info.
410 fail_bad_nri: Tamper with new rootfs info.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700411 fail_old_kernel_fs_size: Make old kernel fs size too big.
412 fail_old_rootfs_fs_size: Make old rootfs fs size too big.
413 fail_new_kernel_fs_size: Make new kernel fs size too big.
414 fail_new_rootfs_fs_size: Make new rootfs fs size too big.
Gilad Arnold5502b562013-03-08 13:22:31 -0800415 """
416 # Generate a test payload. For this test, we only care about the manifest
417 # and don't need any data blobs, hence we can use a plain paylaod generator
418 # (which also gives us more control on things that can be screwed up).
419 payload_gen = test_utils.PayloadGenerator()
420
421 # Tamper with block size, if required.
422 if fail_mismatched_block_size:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700423 payload_gen.SetBlockSize(test_utils.KiB(1))
Gilad Arnold5502b562013-03-08 13:22:31 -0800424 else:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700425 payload_gen.SetBlockSize(test_utils.KiB(4))
Gilad Arnold5502b562013-03-08 13:22:31 -0800426
427 # Add some operations.
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800428 payload_gen.AddOperation(False, common.OpType.MOVE,
429 src_extents=[(0, 16), (16, 497)],
430 dst_extents=[(16, 496), (0, 16)])
431 payload_gen.AddOperation(True, common.OpType.MOVE,
432 src_extents=[(0, 8), (8, 8)],
433 dst_extents=[(8, 8), (0, 8)])
Gilad Arnold5502b562013-03-08 13:22:31 -0800434
435 # Set an invalid signatures block (offset but no size), if required.
436 if fail_bad_sigs:
437 payload_gen.SetSignatures(32, None)
438
Gilad Arnold382df5c2013-05-03 12:49:28 -0700439 # Set partition / filesystem sizes.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700440 rootfs_part_size = test_utils.MiB(8)
441 kernel_part_size = test_utils.KiB(512)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700442 old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size
443 old_kernel_fs_size = new_kernel_fs_size = kernel_part_size
444 if fail_old_kernel_fs_size:
445 old_kernel_fs_size += 100
446 if fail_old_rootfs_fs_size:
447 old_rootfs_fs_size += 100
448 if fail_new_kernel_fs_size:
449 new_kernel_fs_size += 100
450 if fail_new_rootfs_fs_size:
451 new_rootfs_fs_size += 100
452
Gilad Arnold5502b562013-03-08 13:22:31 -0800453 # Add old kernel/rootfs partition info, as required.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700454 if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki:
Gilad Arnold5502b562013-03-08 13:22:31 -0800455 oki_hash = (None if fail_bad_oki
456 else hashlib.sha256('fake-oki-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700457 payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash)
458 if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or
459 fail_bad_ori):
460 ori_hash = (None if fail_bad_ori
461 else hashlib.sha256('fake-ori-content').digest())
462 payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash)
Gilad Arnold5502b562013-03-08 13:22:31 -0800463
464 # Add new kernel/rootfs partition info.
465 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700466 True, True, new_kernel_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800467 None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest())
468 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700469 False, True, new_rootfs_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800470 None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest())
471
472 # Create the test object.
473 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile)
474 report = checker._PayloadReport()
475
476 should_fail = (fail_mismatched_block_size or fail_bad_sigs or
477 fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or
Gilad Arnold5bc7fbe2015-02-05 13:01:09 -0800478 fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or
479 fail_old_rootfs_fs_size or fail_new_kernel_fs_size or
480 fail_new_rootfs_fs_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800481 if should_fail:
482 self.assertRaises(update_payload.PayloadError,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700483 payload_checker._CheckManifest, report,
484 rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800485 else:
Gilad Arnold382df5c2013-05-03 12:49:28 -0700486 self.assertIsNone(payload_checker._CheckManifest(report,
487 rootfs_part_size,
488 kernel_part_size))
Gilad Arnold5502b562013-03-08 13:22:31 -0800489
490 def testCheckLength(self):
491 """Tests _CheckLength()."""
492 payload_checker = checker.PayloadChecker(self.MockPayload())
493 block_size = payload_checker.block_size
494
495 # Passes.
496 self.assertIsNone(payload_checker._CheckLength(
497 int(3.5 * block_size), 4, 'foo', 'bar'))
498 # Fails, too few blocks.
499 self.assertRaises(update_payload.PayloadError,
500 payload_checker._CheckLength,
501 int(3.5 * block_size), 3, 'foo', 'bar')
502 # Fails, too many blocks.
503 self.assertRaises(update_payload.PayloadError,
504 payload_checker._CheckLength,
505 int(3.5 * block_size), 5, 'foo', 'bar')
506
507 def testCheckExtents(self):
508 """Tests _CheckExtents()."""
509 payload_checker = checker.PayloadChecker(self.MockPayload())
510 block_size = payload_checker.block_size
511
512 # Passes w/ all real extents.
513 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
514 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700515 23,
Gilad Arnold5502b562013-03-08 13:22:31 -0800516 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700517 collections.defaultdict(int), 'foo'))
Gilad Arnold5502b562013-03-08 13:22:31 -0800518
519 # Passes w/ pseudo-extents (aka sparse holes).
520 extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5),
521 (8, 3))
522 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700523 12,
Gilad Arnold5502b562013-03-08 13:22:31 -0800524 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
525 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700526 allow_pseudo=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800527
528 # Passes w/ pseudo-extent due to a signature.
529 extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2))
530 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700531 2,
Gilad Arnold5502b562013-03-08 13:22:31 -0800532 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
533 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700534 allow_signature=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800535
536 # Fails, extent missing a start block.
537 extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16))
538 self.assertRaises(
539 update_payload.PayloadError, payload_checker._CheckExtents,
540 extents, (1024 + 16) * block_size, collections.defaultdict(int),
541 'foo')
542
543 # Fails, extent missing block count.
544 extents = self.NewExtentList((0, -1), (8, 3), (1024, 16))
545 self.assertRaises(
546 update_payload.PayloadError, payload_checker._CheckExtents,
547 extents, (1024 + 16) * block_size, collections.defaultdict(int),
548 'foo')
549
550 # Fails, extent has zero blocks.
551 extents = self.NewExtentList((0, 4), (8, 3), (1024, 0))
552 self.assertRaises(
553 update_payload.PayloadError, payload_checker._CheckExtents,
554 extents, (1024 + 16) * block_size, collections.defaultdict(int),
555 'foo')
556
557 # Fails, extent exceeds partition boundaries.
558 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
559 self.assertRaises(
560 update_payload.PayloadError, payload_checker._CheckExtents,
561 extents, (1024 + 15) * block_size, collections.defaultdict(int),
562 'foo')
563
564 def testCheckReplaceOperation(self):
565 """Tests _CheckReplaceOperation() where op.type == REPLACE."""
566 payload_checker = checker.PayloadChecker(self.MockPayload())
567 block_size = payload_checker.block_size
568 data_length = 10000
569
570 op = self.mox.CreateMock(
571 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
572 op.type = common.OpType.REPLACE
573
574 # Pass.
575 op.src_extents = []
576 self.assertIsNone(
577 payload_checker._CheckReplaceOperation(
578 op, data_length, (data_length + block_size - 1) / block_size,
579 'foo'))
580
581 # Fail, src extents founds.
582 op.src_extents = ['bar']
583 self.assertRaises(
584 update_payload.PayloadError,
585 payload_checker._CheckReplaceOperation,
586 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
587
588 # Fail, missing data.
589 op.src_extents = []
590 self.assertRaises(
591 update_payload.PayloadError,
592 payload_checker._CheckReplaceOperation,
593 op, None, (data_length + block_size - 1) / block_size, 'foo')
594
595 # Fail, length / block number mismatch.
596 op.src_extents = ['bar']
597 self.assertRaises(
598 update_payload.PayloadError,
599 payload_checker._CheckReplaceOperation,
600 op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo')
601
602 def testCheckReplaceBzOperation(self):
603 """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ."""
604 payload_checker = checker.PayloadChecker(self.MockPayload())
605 block_size = payload_checker.block_size
606 data_length = block_size * 3
607
608 op = self.mox.CreateMock(
609 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
610 op.type = common.OpType.REPLACE_BZ
611
612 # Pass.
613 op.src_extents = []
614 self.assertIsNone(
615 payload_checker._CheckReplaceOperation(
616 op, data_length, (data_length + block_size - 1) / block_size + 5,
617 'foo'))
618
619 # Fail, src extents founds.
620 op.src_extents = ['bar']
621 self.assertRaises(
622 update_payload.PayloadError,
623 payload_checker._CheckReplaceOperation,
624 op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
625
626 # Fail, missing data.
627 op.src_extents = []
628 self.assertRaises(
629 update_payload.PayloadError,
630 payload_checker._CheckReplaceOperation,
631 op, None, (data_length + block_size - 1) / block_size, 'foo')
632
633 # Fail, too few blocks to justify BZ.
634 op.src_extents = []
635 self.assertRaises(
636 update_payload.PayloadError,
637 payload_checker._CheckReplaceOperation,
638 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
639
640 def testCheckMoveOperation_Pass(self):
641 """Tests _CheckMoveOperation(); pass case."""
642 payload_checker = checker.PayloadChecker(self.MockPayload())
643 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
644 op.type = common.OpType.MOVE
645
646 self.AddToMessage(op.src_extents,
647 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
648 self.AddToMessage(op.dst_extents,
649 self.NewExtentList((16, 128), (512, 6)))
650 self.assertIsNone(
651 payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo'))
652
653 def testCheckMoveOperation_FailContainsData(self):
654 """Tests _CheckMoveOperation(); fails, message contains data."""
655 payload_checker = checker.PayloadChecker(self.MockPayload())
656 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
657 op.type = common.OpType.MOVE
658
659 self.AddToMessage(op.src_extents,
660 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
661 self.AddToMessage(op.dst_extents,
662 self.NewExtentList((16, 128), (512, 6)))
663 self.assertRaises(
664 update_payload.PayloadError,
665 payload_checker._CheckMoveOperation,
666 op, 1024, 134, 134, 'foo')
667
668 def testCheckMoveOperation_FailInsufficientSrcBlocks(self):
669 """Tests _CheckMoveOperation(); fails, not enough actual src blocks."""
670 payload_checker = checker.PayloadChecker(self.MockPayload())
671 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
672 op.type = common.OpType.MOVE
673
674 self.AddToMessage(op.src_extents,
675 self.NewExtentList((0, 4), (12, 2), (1024, 127)))
676 self.AddToMessage(op.dst_extents,
677 self.NewExtentList((16, 128), (512, 6)))
678 self.assertRaises(
679 update_payload.PayloadError,
680 payload_checker._CheckMoveOperation,
681 op, None, 134, 134, 'foo')
682
683 def testCheckMoveOperation_FailInsufficientDstBlocks(self):
684 """Tests _CheckMoveOperation(); fails, not enough actual dst blocks."""
685 payload_checker = checker.PayloadChecker(self.MockPayload())
686 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
687 op.type = common.OpType.MOVE
688
689 self.AddToMessage(op.src_extents,
690 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
691 self.AddToMessage(op.dst_extents,
692 self.NewExtentList((16, 128), (512, 5)))
693 self.assertRaises(
694 update_payload.PayloadError,
695 payload_checker._CheckMoveOperation,
696 op, None, 134, 134, 'foo')
697
698 def testCheckMoveOperation_FailExcessSrcBlocks(self):
699 """Tests _CheckMoveOperation(); fails, too many actual src blocks."""
700 payload_checker = checker.PayloadChecker(self.MockPayload())
701 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
702 op.type = common.OpType.MOVE
703
704 self.AddToMessage(op.src_extents,
705 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
706 self.AddToMessage(op.dst_extents,
707 self.NewExtentList((16, 128), (512, 5)))
708 self.assertRaises(
709 update_payload.PayloadError,
710 payload_checker._CheckMoveOperation,
711 op, None, 134, 134, 'foo')
712 self.AddToMessage(op.src_extents,
713 self.NewExtentList((0, 4), (12, 2), (1024, 129)))
714 self.AddToMessage(op.dst_extents,
715 self.NewExtentList((16, 128), (512, 6)))
716 self.assertRaises(
717 update_payload.PayloadError,
718 payload_checker._CheckMoveOperation,
719 op, None, 134, 134, 'foo')
720
721 def testCheckMoveOperation_FailExcessDstBlocks(self):
722 """Tests _CheckMoveOperation(); fails, too many actual dst blocks."""
723 payload_checker = checker.PayloadChecker(self.MockPayload())
724 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
725 op.type = common.OpType.MOVE
726
727 self.AddToMessage(op.src_extents,
728 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
729 self.AddToMessage(op.dst_extents,
730 self.NewExtentList((16, 128), (512, 7)))
731 self.assertRaises(
732 update_payload.PayloadError,
733 payload_checker._CheckMoveOperation,
734 op, None, 134, 134, 'foo')
735
736 def testCheckMoveOperation_FailStagnantBlocks(self):
737 """Tests _CheckMoveOperation(); fails, there are blocks that do not move."""
738 payload_checker = checker.PayloadChecker(self.MockPayload())
739 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
740 op.type = common.OpType.MOVE
741
742 self.AddToMessage(op.src_extents,
743 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
744 self.AddToMessage(op.dst_extents,
745 self.NewExtentList((8, 128), (512, 6)))
746 self.assertRaises(
747 update_payload.PayloadError,
748 payload_checker._CheckMoveOperation,
749 op, None, 134, 134, 'foo')
750
751 def testCheckBsdiff(self):
752 """Tests _CheckMoveOperation()."""
753 payload_checker = checker.PayloadChecker(self.MockPayload())
754
755 # Pass.
756 self.assertIsNone(
757 payload_checker._CheckBsdiffOperation(10000, 3, 'foo'))
758
759 # Fail, missing data blob.
760 self.assertRaises(
761 update_payload.PayloadError,
762 payload_checker._CheckBsdiffOperation,
763 None, 3, 'foo')
764
765 # Fail, too big of a diff blob (unjustified).
766 self.assertRaises(
767 update_payload.PayloadError,
768 payload_checker._CheckBsdiffOperation,
769 10000, 2, 'foo')
770
771 def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
772 allow_unhashed, fail_src_extents, fail_dst_extents,
773 fail_mismatched_data_offset_length,
774 fail_missing_dst_extents, fail_src_length,
775 fail_dst_length, fail_data_hash,
776 fail_prev_data_offset):
777 """Parametric testing of _CheckOperation().
778
779 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700780 op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE' or 'BSDIFF'.
781 is_last: Whether we're testing the last operation in a sequence.
782 allow_signature: Whether we're testing a signature-capable operation.
783 allow_unhashed: Whether we're allowing to not hash the data.
784 fail_src_extents: Tamper with src extents.
785 fail_dst_extents: Tamper with dst extents.
786 fail_mismatched_data_offset_length: Make data_{offset,length}
787 inconsistent.
788 fail_missing_dst_extents: Do not include dst extents.
789 fail_src_length: Make src length inconsistent.
790 fail_dst_length: Make dst length inconsistent.
791 fail_data_hash: Tamper with the data blob hash.
792 fail_prev_data_offset: Make data space uses incontiguous.
Gilad Arnold5502b562013-03-08 13:22:31 -0800793 """
794 op_type = _OpTypeByName(op_type_name)
795
796 # Create the test object.
797 payload = self.MockPayload()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700798 payload_checker = checker.PayloadChecker(payload,
799 allow_unhashed=allow_unhashed)
Gilad Arnold5502b562013-03-08 13:22:31 -0800800 block_size = payload_checker.block_size
801
802 # Create auxiliary arguments.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700803 old_part_size = test_utils.MiB(4)
804 new_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800805 old_block_counters = array.array(
806 'B', [0] * ((old_part_size + block_size - 1) / block_size))
807 new_block_counters = array.array(
808 'B', [0] * ((new_part_size + block_size - 1) / block_size))
809 prev_data_offset = 1876
810 blob_hash_counts = collections.defaultdict(int)
811
812 # Create the operation object for the test.
813 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
814 op.type = op_type
815
816 total_src_blocks = 0
817 if op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
818 if fail_src_extents:
819 self.AddToMessage(op.src_extents,
820 self.NewExtentList((0, 0)))
821 else:
822 self.AddToMessage(op.src_extents,
823 self.NewExtentList((0, 16)))
824 total_src_blocks = 16
825
826 if op_type != common.OpType.MOVE:
827 if not fail_mismatched_data_offset_length:
828 op.data_length = 16 * block_size - 8
829 if fail_prev_data_offset:
830 op.data_offset = prev_data_offset + 16
831 else:
832 op.data_offset = prev_data_offset
833
834 fake_data = 'fake-data'.ljust(op.data_length)
835 if not (allow_unhashed or (is_last and allow_signature and
836 op_type == common.OpType.REPLACE)):
837 if not fail_data_hash:
838 # Create a valid data blob hash.
839 op.data_sha256_hash = hashlib.sha256(fake_data).digest()
840 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
841 fake_data)
842 elif fail_data_hash:
843 # Create an invalid data blob hash.
844 op.data_sha256_hash = hashlib.sha256(
845 fake_data.replace(' ', '-')).digest()
846 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
847 fake_data)
848
849 total_dst_blocks = 0
850 if not fail_missing_dst_extents:
851 total_dst_blocks = 16
852 if fail_dst_extents:
853 self.AddToMessage(op.dst_extents,
854 self.NewExtentList((4, 16), (32, 0)))
855 else:
856 self.AddToMessage(op.dst_extents,
857 self.NewExtentList((4, 8), (64, 8)))
858
859 if total_src_blocks:
860 if fail_src_length:
861 op.src_length = total_src_blocks * block_size + 8
862 else:
863 op.src_length = total_src_blocks * block_size
864 elif fail_src_length:
865 # Add an orphaned src_length.
866 op.src_length = 16
867
868 if total_dst_blocks:
869 if fail_dst_length:
870 op.dst_length = total_dst_blocks * block_size + 8
871 else:
872 op.dst_length = total_dst_blocks * block_size
873
874 self.mox.ReplayAll()
875 should_fail = (fail_src_extents or fail_dst_extents or
876 fail_mismatched_data_offset_length or
877 fail_missing_dst_extents or fail_src_length or
878 fail_dst_length or fail_data_hash or fail_prev_data_offset)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700879 args = (op, 'foo', is_last, old_block_counters, new_block_counters,
880 old_part_size, new_part_size, prev_data_offset, allow_signature,
881 blob_hash_counts)
Gilad Arnold5502b562013-03-08 13:22:31 -0800882 if should_fail:
883 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700884 payload_checker._CheckOperation, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800885 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700886 self.assertEqual(op.data_length if op.HasField('data_length') else 0,
887 payload_checker._CheckOperation(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800888
889 def testAllocBlockCounters(self):
890 """Tests _CheckMoveOperation()."""
891 payload_checker = checker.PayloadChecker(self.MockPayload())
892 block_size = payload_checker.block_size
893
894 # Check allocation for block-aligned partition size, ensure it's integers.
895 result = payload_checker._AllocBlockCounters(16 * block_size)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700896 self.assertEqual(16, len(result))
897 self.assertEqual(int, type(result[0]))
Gilad Arnold5502b562013-03-08 13:22:31 -0800898
899 # Check allocation of unaligned partition sizes.
900 result = payload_checker._AllocBlockCounters(16 * block_size - 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700901 self.assertEqual(16, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800902 result = payload_checker._AllocBlockCounters(16 * block_size + 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700903 self.assertEqual(17, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800904
905 def DoCheckOperationsTest(self, fail_bad_type,
906 fail_nonexhaustive_full_update):
907 # Generate a test payload. For this test, we only care about one
908 # (arbitrary) set of operations, so we'll only be generating kernel and
909 # test with them.
910 payload_gen = test_utils.PayloadGenerator()
911
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700912 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800913 payload_gen.SetBlockSize(block_size)
914
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700915 rootfs_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800916
917 # Fake rootfs operations in a full update, tampered with as required.
918 rootfs_op_type = common.OpType.REPLACE
919 if fail_bad_type:
920 # Choose a type value that's bigger than the highest valid value.
921 for valid_op_type in common.OpType.ALL:
922 rootfs_op_type = max(rootfs_op_type, valid_op_type)
923 rootfs_op_type += 1
924
925 rootfs_data_length = rootfs_part_size
926 if fail_nonexhaustive_full_update:
927 rootfs_data_length -= block_size
928
929 payload_gen.AddOperation(False, rootfs_op_type,
930 dst_extents=[(0, rootfs_data_length / block_size)],
931 data_offset=0,
932 data_length=rootfs_data_length)
933
934 # Create the test object.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700935 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
936 checker_init_dargs={
937 'allow_unhashed': True})
Gilad Arnold5502b562013-03-08 13:22:31 -0800938 payload_checker.payload_type = checker._TYPE_FULL
939 report = checker._PayloadReport()
940
941 should_fail = (fail_bad_type or fail_nonexhaustive_full_update)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700942 args = (payload_checker.payload.manifest.install_operations, report,
943 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
Gilad Arnold5502b562013-03-08 13:22:31 -0800944 if should_fail:
945 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700946 payload_checker._CheckOperations, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800947 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700948 self.assertEqual(rootfs_data_length,
949 payload_checker._CheckOperations(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800950
951 def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
952 fail_mismatched_pseudo_op, fail_sig_missing_fields,
953 fail_unknown_sig_version, fail_incorrect_sig):
954 # Generate a test payload. For this test, we only care about the signature
955 # block and how it relates to the payload hash. Therefore, we're generating
956 # a random (otherwise useless) payload for this purpose.
957 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700958 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800959 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700960 rootfs_part_size = test_utils.MiB(2)
961 kernel_part_size = test_utils.KiB(16)
Gilad Arnold5502b562013-03-08 13:22:31 -0800962 payload_gen.SetPartInfo(False, True, rootfs_part_size,
963 hashlib.sha256('fake-new-rootfs-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700964 payload_gen.SetPartInfo(True, True, kernel_part_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800965 hashlib.sha256('fake-new-kernel-content').digest())
966 payload_gen.AddOperationWithData(
967 False, common.OpType.REPLACE,
968 dst_extents=[(0, rootfs_part_size / block_size)],
969 data_blob=os.urandom(rootfs_part_size))
970
971 do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
972 do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
973 fail_sig_missing_fields or fail_unknown_sig_version
974 or fail_incorrect_sig)
975
976 sigs_data = None
977 if do_forge_sigs_data:
978 sigs_gen = test_utils.SignaturesGenerator()
979 if not fail_empty_sigs_blob:
980 if fail_sig_missing_fields:
981 sig_data = None
982 else:
983 sig_data = test_utils.SignSha256('fake-payload-content',
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700984 test_utils._PRIVKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -0800985 sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
986
987 sigs_data = sigs_gen.ToBinary()
988 payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
989
990 if do_forge_pseudo_op:
991 assert sigs_data is not None, 'should have forged signatures blob by now'
992 sigs_len = len(sigs_data)
993 payload_gen.AddOperation(
994 False, common.OpType.REPLACE,
995 data_offset=payload_gen.curr_offset / 2,
996 data_length=sigs_len / 2,
997 dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
998
999 # Generate payload (complete w/ signature) and create the test object.
1000 payload_checker = _GetPayloadChecker(
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001001 payload_gen.WriteToFileWithData,
1002 payload_gen_dargs={
1003 'sigs_data': sigs_data,
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001004 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001005 'do_add_pseudo_operation': not do_forge_pseudo_op})
Gilad Arnold5502b562013-03-08 13:22:31 -08001006 payload_checker.payload_type = checker._TYPE_FULL
1007 report = checker._PayloadReport()
1008
1009 # We have to check the manifest first in order to set signature attributes.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001010 payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -08001011
1012 should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
1013 fail_mismatched_pseudo_op or fail_sig_missing_fields or
1014 fail_unknown_sig_version or fail_incorrect_sig)
Gilad Arnoldcb638912013-06-24 04:57:11 -07001015 args = (report, test_utils._PUBKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001016 if should_fail:
1017 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001018 payload_checker._CheckSignatures, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -08001019 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001020 self.assertIsNone(payload_checker._CheckSignatures(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -08001021
1022 def DoRunTest(self, fail_wrong_payload_type, fail_invalid_block_size,
1023 fail_mismatched_block_size, fail_excess_data):
1024 # Generate a test payload. For this test, we generate a full update that
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001025 # has sample kernel and rootfs operations. Since most testing is done with
Gilad Arnold5502b562013-03-08 13:22:31 -08001026 # internal PayloadChecker methods that are tested elsewhere, here we only
1027 # tamper with what's actually being manipulated and/or tested in the Run()
1028 # method itself. Note that the checker doesn't verify partition hashes, so
1029 # they're safe to fake.
1030 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001031 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001032 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001033 kernel_part_size = test_utils.KiB(16)
1034 rootfs_part_size = test_utils.MiB(2)
Gilad Arnold5502b562013-03-08 13:22:31 -08001035 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1036 hashlib.sha256('fake-new-rootfs-content').digest())
1037 payload_gen.SetPartInfo(True, True, kernel_part_size,
1038 hashlib.sha256('fake-new-kernel-content').digest())
1039 payload_gen.AddOperationWithData(
1040 False, common.OpType.REPLACE,
1041 dst_extents=[(0, rootfs_part_size / block_size)],
1042 data_blob=os.urandom(rootfs_part_size))
1043 payload_gen.AddOperationWithData(
1044 True, common.OpType.REPLACE,
1045 dst_extents=[(0, kernel_part_size / block_size)],
1046 data_blob=os.urandom(kernel_part_size))
1047
1048 # Generate payload (complete w/ signature) and create the test object.
Gilad Arnold5502b562013-03-08 13:22:31 -08001049 if fail_invalid_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001050 use_block_size = block_size + 5 # Not a power of two.
Gilad Arnold5502b562013-03-08 13:22:31 -08001051 elif fail_mismatched_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001052 use_block_size = block_size * 2 # Different that payload stated.
Gilad Arnold5502b562013-03-08 13:22:31 -08001053 else:
1054 use_block_size = block_size
Gilad Arnold5502b562013-03-08 13:22:31 -08001055
Gilad Arnoldcb638912013-06-24 04:57:11 -07001056 kwargs = {
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001057 'payload_gen_dargs': {
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001058 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001059 'do_add_pseudo_operation': True,
1060 'is_pseudo_in_kernel': True,
1061 'padding': os.urandom(1024) if fail_excess_data else None},
1062 'checker_init_dargs': {
1063 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
1064 'block_size': use_block_size}}
1065 if fail_invalid_block_size:
1066 self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001067 payload_gen.WriteToFileWithData, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -08001068 else:
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001069 payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001070 **kwargs)
1071 kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME}
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001072 should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
1073 fail_excess_data)
1074 if should_fail:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001075 self.assertRaises(update_payload.PayloadError, payload_checker.Run,
1076 **kwargs)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001077 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001078 self.assertIsNone(payload_checker.Run(**kwargs))
Gilad Arnold5502b562013-03-08 13:22:31 -08001079
1080
1081# This implements a generic API, hence the occasional unused args.
1082# pylint: disable=W0613
1083def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
1084 allow_unhashed, fail_src_extents,
1085 fail_dst_extents,
1086 fail_mismatched_data_offset_length,
1087 fail_missing_dst_extents, fail_src_length,
1088 fail_dst_length, fail_data_hash,
1089 fail_prev_data_offset):
1090 """Returns True iff the combination of arguments represents a valid test."""
1091 op_type = _OpTypeByName(op_type_name)
1092
1093 # REPLACE/REPLACE_BZ operations don't read data from src partition.
1094 if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
1095 fail_src_extents or fail_src_length)):
1096 return False
1097
1098 # MOVE operations don't carry data.
1099 if (op_type == common.OpType.MOVE and (
1100 fail_mismatched_data_offset_length or fail_data_hash or
1101 fail_prev_data_offset)):
1102 return False
1103
1104 return True
1105
1106
1107def TestMethodBody(run_method_name, run_dargs):
1108 """Returns a function that invokes a named method with named arguments."""
1109 return lambda self: getattr(self, run_method_name)(**run_dargs)
1110
1111
1112def AddParametricTests(tested_method_name, arg_space, validate_func=None):
1113 """Enumerates and adds specific parametric tests to PayloadCheckerTest.
1114
1115 This function enumerates a space of test parameters (defined by arg_space),
1116 then binds a new, unique method name in PayloadCheckerTest to a test function
1117 that gets handed the said parameters. This is a preferable approach to doing
1118 the enumeration and invocation during the tests because this way each test is
1119 treated as a complete run by the unittest framework, and so benefits from the
1120 usual setUp/tearDown mechanics.
1121
1122 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001123 tested_method_name: Name of the tested PayloadChecker method.
1124 arg_space: A dictionary containing variables (keys) and lists of values
1125 (values) associated with them.
1126 validate_func: A function used for validating test argument combinations.
Gilad Arnold5502b562013-03-08 13:22:31 -08001127 """
1128 for value_tuple in itertools.product(*arg_space.itervalues()):
1129 run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
1130 if validate_func and not validate_func(**run_dargs):
1131 continue
1132 run_method_name = 'Do%sTest' % tested_method_name
1133 test_method_name = 'test%s' % tested_method_name
1134 for arg_key, arg_val in run_dargs.iteritems():
1135 if arg_val or type(arg_val) is int:
1136 test_method_name += '__%s=%s' % (arg_key, arg_val)
1137 setattr(PayloadCheckerTest, test_method_name,
1138 TestMethodBody(run_method_name, run_dargs))
1139
1140
1141def AddAllParametricTests():
1142 """Enumerates and adds all parametric tests to PayloadCheckerTest."""
1143 # Add all _CheckElem() test cases.
1144 AddParametricTests('AddElem',
1145 {'linebreak': (True, False),
1146 'indent': (0, 1, 2),
1147 'convert': (str, lambda s: s[::-1]),
1148 'is_present': (True, False),
1149 'is_mandatory': (True, False),
1150 'is_submsg': (True, False)})
1151
1152 # Add all _Add{Mandatory,Optional}Field tests.
1153 AddParametricTests('AddField',
1154 {'is_mandatory': (True, False),
1155 'linebreak': (True, False),
1156 'indent': (0, 1, 2),
1157 'convert': (str, lambda s: s[::-1]),
1158 'is_present': (True, False)})
1159
1160 # Add all _Add{Mandatory,Optional}SubMsg tests.
1161 AddParametricTests('AddSubMsg',
1162 {'is_mandatory': (True, False),
1163 'is_present': (True, False)})
1164
1165 # Add all _CheckManifest() test cases.
1166 AddParametricTests('CheckManifest',
1167 {'fail_mismatched_block_size': (True, False),
1168 'fail_bad_sigs': (True, False),
1169 'fail_mismatched_oki_ori': (True, False),
1170 'fail_bad_oki': (True, False),
1171 'fail_bad_ori': (True, False),
1172 'fail_bad_nki': (True, False),
1173 'fail_bad_nri': (True, False),
Gilad Arnold382df5c2013-05-03 12:49:28 -07001174 'fail_old_kernel_fs_size': (True, False),
1175 'fail_old_rootfs_fs_size': (True, False),
1176 'fail_new_kernel_fs_size': (True, False),
1177 'fail_new_rootfs_fs_size': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001178
1179 # Add all _CheckOperation() test cases.
1180 AddParametricTests('CheckOperation',
1181 {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
1182 'BSDIFF'),
1183 'is_last': (True, False),
1184 'allow_signature': (True, False),
1185 'allow_unhashed': (True, False),
1186 'fail_src_extents': (True, False),
1187 'fail_dst_extents': (True, False),
1188 'fail_mismatched_data_offset_length': (True, False),
1189 'fail_missing_dst_extents': (True, False),
1190 'fail_src_length': (True, False),
1191 'fail_dst_length': (True, False),
1192 'fail_data_hash': (True, False),
1193 'fail_prev_data_offset': (True, False)},
1194 validate_func=ValidateCheckOperationTest)
1195
1196 # Add all _CheckOperations() test cases.
1197 AddParametricTests('CheckOperations',
1198 {'fail_bad_type': (True, False),
1199 'fail_nonexhaustive_full_update': (True, False)})
1200
1201 # Add all _CheckOperations() test cases.
1202 AddParametricTests('CheckSignatures',
1203 {'fail_empty_sigs_blob': (True, False),
1204 'fail_missing_pseudo_op': (True, False),
1205 'fail_mismatched_pseudo_op': (True, False),
1206 'fail_sig_missing_fields': (True, False),
1207 'fail_unknown_sig_version': (True, False),
1208 'fail_incorrect_sig': (True, False)})
1209
1210 # Add all Run() test cases.
1211 AddParametricTests('Run',
1212 {'fail_wrong_payload_type': (True, False),
1213 'fail_invalid_block_size': (True, False),
1214 'fail_mismatched_block_size': (True, False),
1215 'fail_excess_data': (True, False)})
1216
1217
1218if __name__ == '__main__':
1219 AddAllParametricTests()
1220 unittest.main()