blob: 9ca5a64d8a36c2334e9f0c0c18de17b6b83a55ac [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 Arnold382df5c2013-05-03 12:49:28 -0700398 fail_bad_nki, fail_bad_nri, fail_missing_ops,
399 fail_old_kernel_fs_size, fail_old_rootfs_fs_size,
400 fail_new_kernel_fs_size, 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.
411 fail_missing_ops: Simulate a manifest without any operations.
412 fail_old_kernel_fs_size: Make old kernel fs size too big.
413 fail_old_rootfs_fs_size: Make old rootfs fs size too big.
414 fail_new_kernel_fs_size: Make new kernel fs size too big.
415 fail_new_rootfs_fs_size: Make new rootfs fs size too big.
Gilad Arnold5502b562013-03-08 13:22:31 -0800416 """
417 # Generate a test payload. For this test, we only care about the manifest
418 # and don't need any data blobs, hence we can use a plain paylaod generator
419 # (which also gives us more control on things that can be screwed up).
420 payload_gen = test_utils.PayloadGenerator()
421
422 # Tamper with block size, if required.
423 if fail_mismatched_block_size:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700424 payload_gen.SetBlockSize(test_utils.KiB(1))
Gilad Arnold5502b562013-03-08 13:22:31 -0800425 else:
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700426 payload_gen.SetBlockSize(test_utils.KiB(4))
Gilad Arnold5502b562013-03-08 13:22:31 -0800427
428 # Add some operations.
429 if not fail_missing_ops:
430 payload_gen.AddOperation(False, common.OpType.MOVE,
431 src_extents=[(0, 16), (16, 497)],
432 dst_extents=[(16, 496), (0, 16)])
433 payload_gen.AddOperation(True, common.OpType.MOVE,
434 src_extents=[(0, 8), (8, 8)],
435 dst_extents=[(8, 8), (0, 8)])
436
437 # Set an invalid signatures block (offset but no size), if required.
438 if fail_bad_sigs:
439 payload_gen.SetSignatures(32, None)
440
Gilad Arnold382df5c2013-05-03 12:49:28 -0700441 # Set partition / filesystem sizes.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700442 rootfs_part_size = test_utils.MiB(8)
443 kernel_part_size = test_utils.KiB(512)
Gilad Arnold382df5c2013-05-03 12:49:28 -0700444 old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size
445 old_kernel_fs_size = new_kernel_fs_size = kernel_part_size
446 if fail_old_kernel_fs_size:
447 old_kernel_fs_size += 100
448 if fail_old_rootfs_fs_size:
449 old_rootfs_fs_size += 100
450 if fail_new_kernel_fs_size:
451 new_kernel_fs_size += 100
452 if fail_new_rootfs_fs_size:
453 new_rootfs_fs_size += 100
454
Gilad Arnold5502b562013-03-08 13:22:31 -0800455 # Add old kernel/rootfs partition info, as required.
Gilad Arnold382df5c2013-05-03 12:49:28 -0700456 if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki:
Gilad Arnold5502b562013-03-08 13:22:31 -0800457 oki_hash = (None if fail_bad_oki
458 else hashlib.sha256('fake-oki-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700459 payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash)
460 if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or
461 fail_bad_ori):
462 ori_hash = (None if fail_bad_ori
463 else hashlib.sha256('fake-ori-content').digest())
464 payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash)
Gilad Arnold5502b562013-03-08 13:22:31 -0800465
466 # Add new kernel/rootfs partition info.
467 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700468 True, True, new_kernel_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800469 None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest())
470 payload_gen.SetPartInfo(
Gilad Arnold382df5c2013-05-03 12:49:28 -0700471 False, True, new_rootfs_fs_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800472 None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest())
473
474 # Create the test object.
475 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile)
476 report = checker._PayloadReport()
477
478 should_fail = (fail_mismatched_block_size or fail_bad_sigs or
479 fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or
Gilad Arnold382df5c2013-05-03 12:49:28 -0700480 fail_bad_nki or fail_bad_nri or fail_missing_ops or
481 fail_old_kernel_fs_size or fail_old_rootfs_fs_size or
482 fail_new_kernel_fs_size or fail_new_rootfs_fs_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800483 if should_fail:
484 self.assertRaises(update_payload.PayloadError,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700485 payload_checker._CheckManifest, report,
486 rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -0800487 else:
Gilad Arnold382df5c2013-05-03 12:49:28 -0700488 self.assertIsNone(payload_checker._CheckManifest(report,
489 rootfs_part_size,
490 kernel_part_size))
Gilad Arnold5502b562013-03-08 13:22:31 -0800491
492 def testCheckLength(self):
493 """Tests _CheckLength()."""
494 payload_checker = checker.PayloadChecker(self.MockPayload())
495 block_size = payload_checker.block_size
496
497 # Passes.
498 self.assertIsNone(payload_checker._CheckLength(
499 int(3.5 * block_size), 4, 'foo', 'bar'))
500 # Fails, too few blocks.
501 self.assertRaises(update_payload.PayloadError,
502 payload_checker._CheckLength,
503 int(3.5 * block_size), 3, 'foo', 'bar')
504 # Fails, too many blocks.
505 self.assertRaises(update_payload.PayloadError,
506 payload_checker._CheckLength,
507 int(3.5 * block_size), 5, 'foo', 'bar')
508
509 def testCheckExtents(self):
510 """Tests _CheckExtents()."""
511 payload_checker = checker.PayloadChecker(self.MockPayload())
512 block_size = payload_checker.block_size
513
514 # Passes w/ all real extents.
515 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
516 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700517 23,
Gilad Arnold5502b562013-03-08 13:22:31 -0800518 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700519 collections.defaultdict(int), 'foo'))
Gilad Arnold5502b562013-03-08 13:22:31 -0800520
521 # Passes w/ pseudo-extents (aka sparse holes).
522 extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5),
523 (8, 3))
524 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700525 12,
Gilad Arnold5502b562013-03-08 13:22:31 -0800526 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
527 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700528 allow_pseudo=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800529
530 # Passes w/ pseudo-extent due to a signature.
531 extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2))
532 self.assertEquals(
Gilad Arnoldcb638912013-06-24 04:57:11 -0700533 2,
Gilad Arnold5502b562013-03-08 13:22:31 -0800534 payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
535 collections.defaultdict(int), 'foo',
Gilad Arnoldcb638912013-06-24 04:57:11 -0700536 allow_signature=True))
Gilad Arnold5502b562013-03-08 13:22:31 -0800537
538 # Fails, extent missing a start block.
539 extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16))
540 self.assertRaises(
541 update_payload.PayloadError, payload_checker._CheckExtents,
542 extents, (1024 + 16) * block_size, collections.defaultdict(int),
543 'foo')
544
545 # Fails, extent missing block count.
546 extents = self.NewExtentList((0, -1), (8, 3), (1024, 16))
547 self.assertRaises(
548 update_payload.PayloadError, payload_checker._CheckExtents,
549 extents, (1024 + 16) * block_size, collections.defaultdict(int),
550 'foo')
551
552 # Fails, extent has zero blocks.
553 extents = self.NewExtentList((0, 4), (8, 3), (1024, 0))
554 self.assertRaises(
555 update_payload.PayloadError, payload_checker._CheckExtents,
556 extents, (1024 + 16) * block_size, collections.defaultdict(int),
557 'foo')
558
559 # Fails, extent exceeds partition boundaries.
560 extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
561 self.assertRaises(
562 update_payload.PayloadError, payload_checker._CheckExtents,
563 extents, (1024 + 15) * block_size, collections.defaultdict(int),
564 'foo')
565
566 def testCheckReplaceOperation(self):
567 """Tests _CheckReplaceOperation() where op.type == REPLACE."""
568 payload_checker = checker.PayloadChecker(self.MockPayload())
569 block_size = payload_checker.block_size
570 data_length = 10000
571
572 op = self.mox.CreateMock(
573 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
574 op.type = common.OpType.REPLACE
575
576 # Pass.
577 op.src_extents = []
578 self.assertIsNone(
579 payload_checker._CheckReplaceOperation(
580 op, data_length, (data_length + block_size - 1) / block_size,
581 'foo'))
582
583 # Fail, src extents founds.
584 op.src_extents = ['bar']
585 self.assertRaises(
586 update_payload.PayloadError,
587 payload_checker._CheckReplaceOperation,
588 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
589
590 # Fail, missing data.
591 op.src_extents = []
592 self.assertRaises(
593 update_payload.PayloadError,
594 payload_checker._CheckReplaceOperation,
595 op, None, (data_length + block_size - 1) / block_size, 'foo')
596
597 # Fail, length / block number mismatch.
598 op.src_extents = ['bar']
599 self.assertRaises(
600 update_payload.PayloadError,
601 payload_checker._CheckReplaceOperation,
602 op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo')
603
604 def testCheckReplaceBzOperation(self):
605 """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ."""
606 payload_checker = checker.PayloadChecker(self.MockPayload())
607 block_size = payload_checker.block_size
608 data_length = block_size * 3
609
610 op = self.mox.CreateMock(
611 update_metadata_pb2.DeltaArchiveManifest.InstallOperation)
612 op.type = common.OpType.REPLACE_BZ
613
614 # Pass.
615 op.src_extents = []
616 self.assertIsNone(
617 payload_checker._CheckReplaceOperation(
618 op, data_length, (data_length + block_size - 1) / block_size + 5,
619 'foo'))
620
621 # Fail, src extents founds.
622 op.src_extents = ['bar']
623 self.assertRaises(
624 update_payload.PayloadError,
625 payload_checker._CheckReplaceOperation,
626 op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
627
628 # Fail, missing data.
629 op.src_extents = []
630 self.assertRaises(
631 update_payload.PayloadError,
632 payload_checker._CheckReplaceOperation,
633 op, None, (data_length + block_size - 1) / block_size, 'foo')
634
635 # Fail, too few blocks to justify BZ.
636 op.src_extents = []
637 self.assertRaises(
638 update_payload.PayloadError,
639 payload_checker._CheckReplaceOperation,
640 op, data_length, (data_length + block_size - 1) / block_size, 'foo')
641
642 def testCheckMoveOperation_Pass(self):
643 """Tests _CheckMoveOperation(); pass case."""
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.assertIsNone(
653 payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo'))
654
655 def testCheckMoveOperation_FailContainsData(self):
656 """Tests _CheckMoveOperation(); fails, message contains data."""
657 payload_checker = checker.PayloadChecker(self.MockPayload())
658 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
659 op.type = common.OpType.MOVE
660
661 self.AddToMessage(op.src_extents,
662 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
663 self.AddToMessage(op.dst_extents,
664 self.NewExtentList((16, 128), (512, 6)))
665 self.assertRaises(
666 update_payload.PayloadError,
667 payload_checker._CheckMoveOperation,
668 op, 1024, 134, 134, 'foo')
669
670 def testCheckMoveOperation_FailInsufficientSrcBlocks(self):
671 """Tests _CheckMoveOperation(); fails, not enough actual src blocks."""
672 payload_checker = checker.PayloadChecker(self.MockPayload())
673 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
674 op.type = common.OpType.MOVE
675
676 self.AddToMessage(op.src_extents,
677 self.NewExtentList((0, 4), (12, 2), (1024, 127)))
678 self.AddToMessage(op.dst_extents,
679 self.NewExtentList((16, 128), (512, 6)))
680 self.assertRaises(
681 update_payload.PayloadError,
682 payload_checker._CheckMoveOperation,
683 op, None, 134, 134, 'foo')
684
685 def testCheckMoveOperation_FailInsufficientDstBlocks(self):
686 """Tests _CheckMoveOperation(); fails, not enough actual dst blocks."""
687 payload_checker = checker.PayloadChecker(self.MockPayload())
688 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
689 op.type = common.OpType.MOVE
690
691 self.AddToMessage(op.src_extents,
692 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
693 self.AddToMessage(op.dst_extents,
694 self.NewExtentList((16, 128), (512, 5)))
695 self.assertRaises(
696 update_payload.PayloadError,
697 payload_checker._CheckMoveOperation,
698 op, None, 134, 134, 'foo')
699
700 def testCheckMoveOperation_FailExcessSrcBlocks(self):
701 """Tests _CheckMoveOperation(); fails, too many actual src blocks."""
702 payload_checker = checker.PayloadChecker(self.MockPayload())
703 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
704 op.type = common.OpType.MOVE
705
706 self.AddToMessage(op.src_extents,
707 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
708 self.AddToMessage(op.dst_extents,
709 self.NewExtentList((16, 128), (512, 5)))
710 self.assertRaises(
711 update_payload.PayloadError,
712 payload_checker._CheckMoveOperation,
713 op, None, 134, 134, 'foo')
714 self.AddToMessage(op.src_extents,
715 self.NewExtentList((0, 4), (12, 2), (1024, 129)))
716 self.AddToMessage(op.dst_extents,
717 self.NewExtentList((16, 128), (512, 6)))
718 self.assertRaises(
719 update_payload.PayloadError,
720 payload_checker._CheckMoveOperation,
721 op, None, 134, 134, 'foo')
722
723 def testCheckMoveOperation_FailExcessDstBlocks(self):
724 """Tests _CheckMoveOperation(); fails, too many actual dst blocks."""
725 payload_checker = checker.PayloadChecker(self.MockPayload())
726 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
727 op.type = common.OpType.MOVE
728
729 self.AddToMessage(op.src_extents,
730 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
731 self.AddToMessage(op.dst_extents,
732 self.NewExtentList((16, 128), (512, 7)))
733 self.assertRaises(
734 update_payload.PayloadError,
735 payload_checker._CheckMoveOperation,
736 op, None, 134, 134, 'foo')
737
738 def testCheckMoveOperation_FailStagnantBlocks(self):
739 """Tests _CheckMoveOperation(); fails, there are blocks that do not move."""
740 payload_checker = checker.PayloadChecker(self.MockPayload())
741 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
742 op.type = common.OpType.MOVE
743
744 self.AddToMessage(op.src_extents,
745 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
746 self.AddToMessage(op.dst_extents,
747 self.NewExtentList((8, 128), (512, 6)))
748 self.assertRaises(
749 update_payload.PayloadError,
750 payload_checker._CheckMoveOperation,
751 op, None, 134, 134, 'foo')
752
753 def testCheckBsdiff(self):
754 """Tests _CheckMoveOperation()."""
755 payload_checker = checker.PayloadChecker(self.MockPayload())
756
757 # Pass.
758 self.assertIsNone(
759 payload_checker._CheckBsdiffOperation(10000, 3, 'foo'))
760
761 # Fail, missing data blob.
762 self.assertRaises(
763 update_payload.PayloadError,
764 payload_checker._CheckBsdiffOperation,
765 None, 3, 'foo')
766
767 # Fail, too big of a diff blob (unjustified).
768 self.assertRaises(
769 update_payload.PayloadError,
770 payload_checker._CheckBsdiffOperation,
771 10000, 2, 'foo')
772
773 def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
774 allow_unhashed, fail_src_extents, fail_dst_extents,
775 fail_mismatched_data_offset_length,
776 fail_missing_dst_extents, fail_src_length,
777 fail_dst_length, fail_data_hash,
778 fail_prev_data_offset):
779 """Parametric testing of _CheckOperation().
780
781 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700782 op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE' or 'BSDIFF'.
783 is_last: Whether we're testing the last operation in a sequence.
784 allow_signature: Whether we're testing a signature-capable operation.
785 allow_unhashed: Whether we're allowing to not hash the data.
786 fail_src_extents: Tamper with src extents.
787 fail_dst_extents: Tamper with dst extents.
788 fail_mismatched_data_offset_length: Make data_{offset,length}
789 inconsistent.
790 fail_missing_dst_extents: Do not include dst extents.
791 fail_src_length: Make src length inconsistent.
792 fail_dst_length: Make dst length inconsistent.
793 fail_data_hash: Tamper with the data blob hash.
794 fail_prev_data_offset: Make data space uses incontiguous.
Gilad Arnold5502b562013-03-08 13:22:31 -0800795 """
796 op_type = _OpTypeByName(op_type_name)
797
798 # Create the test object.
799 payload = self.MockPayload()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700800 payload_checker = checker.PayloadChecker(payload,
801 allow_unhashed=allow_unhashed)
Gilad Arnold5502b562013-03-08 13:22:31 -0800802 block_size = payload_checker.block_size
803
804 # Create auxiliary arguments.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700805 old_part_size = test_utils.MiB(4)
806 new_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800807 old_block_counters = array.array(
808 'B', [0] * ((old_part_size + block_size - 1) / block_size))
809 new_block_counters = array.array(
810 'B', [0] * ((new_part_size + block_size - 1) / block_size))
811 prev_data_offset = 1876
812 blob_hash_counts = collections.defaultdict(int)
813
814 # Create the operation object for the test.
815 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
816 op.type = op_type
817
818 total_src_blocks = 0
819 if op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
820 if fail_src_extents:
821 self.AddToMessage(op.src_extents,
822 self.NewExtentList((0, 0)))
823 else:
824 self.AddToMessage(op.src_extents,
825 self.NewExtentList((0, 16)))
826 total_src_blocks = 16
827
828 if op_type != common.OpType.MOVE:
829 if not fail_mismatched_data_offset_length:
830 op.data_length = 16 * block_size - 8
831 if fail_prev_data_offset:
832 op.data_offset = prev_data_offset + 16
833 else:
834 op.data_offset = prev_data_offset
835
836 fake_data = 'fake-data'.ljust(op.data_length)
837 if not (allow_unhashed or (is_last and allow_signature and
838 op_type == common.OpType.REPLACE)):
839 if not fail_data_hash:
840 # Create a valid data blob hash.
841 op.data_sha256_hash = hashlib.sha256(fake_data).digest()
842 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
843 fake_data)
844 elif fail_data_hash:
845 # Create an invalid data blob hash.
846 op.data_sha256_hash = hashlib.sha256(
847 fake_data.replace(' ', '-')).digest()
848 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
849 fake_data)
850
851 total_dst_blocks = 0
852 if not fail_missing_dst_extents:
853 total_dst_blocks = 16
854 if fail_dst_extents:
855 self.AddToMessage(op.dst_extents,
856 self.NewExtentList((4, 16), (32, 0)))
857 else:
858 self.AddToMessage(op.dst_extents,
859 self.NewExtentList((4, 8), (64, 8)))
860
861 if total_src_blocks:
862 if fail_src_length:
863 op.src_length = total_src_blocks * block_size + 8
864 else:
865 op.src_length = total_src_blocks * block_size
866 elif fail_src_length:
867 # Add an orphaned src_length.
868 op.src_length = 16
869
870 if total_dst_blocks:
871 if fail_dst_length:
872 op.dst_length = total_dst_blocks * block_size + 8
873 else:
874 op.dst_length = total_dst_blocks * block_size
875
876 self.mox.ReplayAll()
877 should_fail = (fail_src_extents or fail_dst_extents or
878 fail_mismatched_data_offset_length or
879 fail_missing_dst_extents or fail_src_length or
880 fail_dst_length or fail_data_hash or fail_prev_data_offset)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700881 args = (op, 'foo', is_last, old_block_counters, new_block_counters,
882 old_part_size, new_part_size, prev_data_offset, allow_signature,
883 blob_hash_counts)
Gilad Arnold5502b562013-03-08 13:22:31 -0800884 if should_fail:
885 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700886 payload_checker._CheckOperation, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800887 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700888 self.assertEqual(op.data_length if op.HasField('data_length') else 0,
889 payload_checker._CheckOperation(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800890
891 def testAllocBlockCounters(self):
892 """Tests _CheckMoveOperation()."""
893 payload_checker = checker.PayloadChecker(self.MockPayload())
894 block_size = payload_checker.block_size
895
896 # Check allocation for block-aligned partition size, ensure it's integers.
897 result = payload_checker._AllocBlockCounters(16 * block_size)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700898 self.assertEqual(16, len(result))
899 self.assertEqual(int, type(result[0]))
Gilad Arnold5502b562013-03-08 13:22:31 -0800900
901 # Check allocation of unaligned partition sizes.
902 result = payload_checker._AllocBlockCounters(16 * block_size - 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700903 self.assertEqual(16, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800904 result = payload_checker._AllocBlockCounters(16 * block_size + 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700905 self.assertEqual(17, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800906
907 def DoCheckOperationsTest(self, fail_bad_type,
908 fail_nonexhaustive_full_update):
909 # Generate a test payload. For this test, we only care about one
910 # (arbitrary) set of operations, so we'll only be generating kernel and
911 # test with them.
912 payload_gen = test_utils.PayloadGenerator()
913
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700914 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800915 payload_gen.SetBlockSize(block_size)
916
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700917 rootfs_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800918
919 # Fake rootfs operations in a full update, tampered with as required.
920 rootfs_op_type = common.OpType.REPLACE
921 if fail_bad_type:
922 # Choose a type value that's bigger than the highest valid value.
923 for valid_op_type in common.OpType.ALL:
924 rootfs_op_type = max(rootfs_op_type, valid_op_type)
925 rootfs_op_type += 1
926
927 rootfs_data_length = rootfs_part_size
928 if fail_nonexhaustive_full_update:
929 rootfs_data_length -= block_size
930
931 payload_gen.AddOperation(False, rootfs_op_type,
932 dst_extents=[(0, rootfs_data_length / block_size)],
933 data_offset=0,
934 data_length=rootfs_data_length)
935
936 # Create the test object.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700937 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
938 checker_init_dargs={
939 'allow_unhashed': True})
Gilad Arnold5502b562013-03-08 13:22:31 -0800940 payload_checker.payload_type = checker._TYPE_FULL
941 report = checker._PayloadReport()
942
943 should_fail = (fail_bad_type or fail_nonexhaustive_full_update)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700944 args = (payload_checker.payload.manifest.install_operations, report,
945 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
Gilad Arnold5502b562013-03-08 13:22:31 -0800946 if should_fail:
947 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700948 payload_checker._CheckOperations, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800949 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700950 self.assertEqual(rootfs_data_length,
951 payload_checker._CheckOperations(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800952
953 def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
954 fail_mismatched_pseudo_op, fail_sig_missing_fields,
955 fail_unknown_sig_version, fail_incorrect_sig):
956 # Generate a test payload. For this test, we only care about the signature
957 # block and how it relates to the payload hash. Therefore, we're generating
958 # a random (otherwise useless) payload for this purpose.
959 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700960 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800961 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700962 rootfs_part_size = test_utils.MiB(2)
963 kernel_part_size = test_utils.KiB(16)
Gilad Arnold5502b562013-03-08 13:22:31 -0800964 payload_gen.SetPartInfo(False, True, rootfs_part_size,
965 hashlib.sha256('fake-new-rootfs-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700966 payload_gen.SetPartInfo(True, True, kernel_part_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800967 hashlib.sha256('fake-new-kernel-content').digest())
968 payload_gen.AddOperationWithData(
969 False, common.OpType.REPLACE,
970 dst_extents=[(0, rootfs_part_size / block_size)],
971 data_blob=os.urandom(rootfs_part_size))
972
973 do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
974 do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
975 fail_sig_missing_fields or fail_unknown_sig_version
976 or fail_incorrect_sig)
977
978 sigs_data = None
979 if do_forge_sigs_data:
980 sigs_gen = test_utils.SignaturesGenerator()
981 if not fail_empty_sigs_blob:
982 if fail_sig_missing_fields:
983 sig_data = None
984 else:
985 sig_data = test_utils.SignSha256('fake-payload-content',
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700986 test_utils._PRIVKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -0800987 sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
988
989 sigs_data = sigs_gen.ToBinary()
990 payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
991
992 if do_forge_pseudo_op:
993 assert sigs_data is not None, 'should have forged signatures blob by now'
994 sigs_len = len(sigs_data)
995 payload_gen.AddOperation(
996 False, common.OpType.REPLACE,
997 data_offset=payload_gen.curr_offset / 2,
998 data_length=sigs_len / 2,
999 dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
1000
1001 # Generate payload (complete w/ signature) and create the test object.
1002 payload_checker = _GetPayloadChecker(
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001003 payload_gen.WriteToFileWithData,
1004 payload_gen_dargs={
1005 'sigs_data': sigs_data,
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001006 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001007 'do_add_pseudo_operation': not do_forge_pseudo_op})
Gilad Arnold5502b562013-03-08 13:22:31 -08001008 payload_checker.payload_type = checker._TYPE_FULL
1009 report = checker._PayloadReport()
1010
1011 # We have to check the manifest first in order to set signature attributes.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001012 payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -08001013
1014 should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
1015 fail_mismatched_pseudo_op or fail_sig_missing_fields or
1016 fail_unknown_sig_version or fail_incorrect_sig)
Gilad Arnoldcb638912013-06-24 04:57:11 -07001017 args = (report, test_utils._PUBKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001018 if should_fail:
1019 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001020 payload_checker._CheckSignatures, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -08001021 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001022 self.assertIsNone(payload_checker._CheckSignatures(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -08001023
1024 def DoRunTest(self, fail_wrong_payload_type, fail_invalid_block_size,
1025 fail_mismatched_block_size, fail_excess_data):
1026 # Generate a test payload. For this test, we generate a full update that
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001027 # has sample kernel and rootfs operations. Since most testing is done with
Gilad Arnold5502b562013-03-08 13:22:31 -08001028 # internal PayloadChecker methods that are tested elsewhere, here we only
1029 # tamper with what's actually being manipulated and/or tested in the Run()
1030 # method itself. Note that the checker doesn't verify partition hashes, so
1031 # they're safe to fake.
1032 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001033 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001034 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001035 kernel_part_size = test_utils.KiB(16)
1036 rootfs_part_size = test_utils.MiB(2)
Gilad Arnold5502b562013-03-08 13:22:31 -08001037 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1038 hashlib.sha256('fake-new-rootfs-content').digest())
1039 payload_gen.SetPartInfo(True, True, kernel_part_size,
1040 hashlib.sha256('fake-new-kernel-content').digest())
1041 payload_gen.AddOperationWithData(
1042 False, common.OpType.REPLACE,
1043 dst_extents=[(0, rootfs_part_size / block_size)],
1044 data_blob=os.urandom(rootfs_part_size))
1045 payload_gen.AddOperationWithData(
1046 True, common.OpType.REPLACE,
1047 dst_extents=[(0, kernel_part_size / block_size)],
1048 data_blob=os.urandom(kernel_part_size))
1049
1050 # Generate payload (complete w/ signature) and create the test object.
Gilad Arnold5502b562013-03-08 13:22:31 -08001051 if fail_invalid_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001052 use_block_size = block_size + 5 # Not a power of two.
Gilad Arnold5502b562013-03-08 13:22:31 -08001053 elif fail_mismatched_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001054 use_block_size = block_size * 2 # Different that payload stated.
Gilad Arnold5502b562013-03-08 13:22:31 -08001055 else:
1056 use_block_size = block_size
Gilad Arnold5502b562013-03-08 13:22:31 -08001057
Gilad Arnoldcb638912013-06-24 04:57:11 -07001058 kwargs = {
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001059 'payload_gen_dargs': {
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001060 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001061 'do_add_pseudo_operation': True,
1062 'is_pseudo_in_kernel': True,
1063 'padding': os.urandom(1024) if fail_excess_data else None},
1064 'checker_init_dargs': {
1065 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
1066 'block_size': use_block_size}}
1067 if fail_invalid_block_size:
1068 self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001069 payload_gen.WriteToFileWithData, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -08001070 else:
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001071 payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001072 **kwargs)
1073 kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME}
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001074 should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
1075 fail_excess_data)
1076 if should_fail:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001077 self.assertRaises(update_payload.PayloadError, payload_checker.Run,
1078 **kwargs)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001079 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001080 self.assertIsNone(payload_checker.Run(**kwargs))
Gilad Arnold5502b562013-03-08 13:22:31 -08001081
1082
1083# This implements a generic API, hence the occasional unused args.
1084# pylint: disable=W0613
1085def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
1086 allow_unhashed, fail_src_extents,
1087 fail_dst_extents,
1088 fail_mismatched_data_offset_length,
1089 fail_missing_dst_extents, fail_src_length,
1090 fail_dst_length, fail_data_hash,
1091 fail_prev_data_offset):
1092 """Returns True iff the combination of arguments represents a valid test."""
1093 op_type = _OpTypeByName(op_type_name)
1094
1095 # REPLACE/REPLACE_BZ operations don't read data from src partition.
1096 if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
1097 fail_src_extents or fail_src_length)):
1098 return False
1099
1100 # MOVE operations don't carry data.
1101 if (op_type == common.OpType.MOVE and (
1102 fail_mismatched_data_offset_length or fail_data_hash or
1103 fail_prev_data_offset)):
1104 return False
1105
1106 return True
1107
1108
1109def TestMethodBody(run_method_name, run_dargs):
1110 """Returns a function that invokes a named method with named arguments."""
1111 return lambda self: getattr(self, run_method_name)(**run_dargs)
1112
1113
1114def AddParametricTests(tested_method_name, arg_space, validate_func=None):
1115 """Enumerates and adds specific parametric tests to PayloadCheckerTest.
1116
1117 This function enumerates a space of test parameters (defined by arg_space),
1118 then binds a new, unique method name in PayloadCheckerTest to a test function
1119 that gets handed the said parameters. This is a preferable approach to doing
1120 the enumeration and invocation during the tests because this way each test is
1121 treated as a complete run by the unittest framework, and so benefits from the
1122 usual setUp/tearDown mechanics.
1123
1124 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001125 tested_method_name: Name of the tested PayloadChecker method.
1126 arg_space: A dictionary containing variables (keys) and lists of values
1127 (values) associated with them.
1128 validate_func: A function used for validating test argument combinations.
Gilad Arnold5502b562013-03-08 13:22:31 -08001129 """
1130 for value_tuple in itertools.product(*arg_space.itervalues()):
1131 run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
1132 if validate_func and not validate_func(**run_dargs):
1133 continue
1134 run_method_name = 'Do%sTest' % tested_method_name
1135 test_method_name = 'test%s' % tested_method_name
1136 for arg_key, arg_val in run_dargs.iteritems():
1137 if arg_val or type(arg_val) is int:
1138 test_method_name += '__%s=%s' % (arg_key, arg_val)
1139 setattr(PayloadCheckerTest, test_method_name,
1140 TestMethodBody(run_method_name, run_dargs))
1141
1142
1143def AddAllParametricTests():
1144 """Enumerates and adds all parametric tests to PayloadCheckerTest."""
1145 # Add all _CheckElem() test cases.
1146 AddParametricTests('AddElem',
1147 {'linebreak': (True, False),
1148 'indent': (0, 1, 2),
1149 'convert': (str, lambda s: s[::-1]),
1150 'is_present': (True, False),
1151 'is_mandatory': (True, False),
1152 'is_submsg': (True, False)})
1153
1154 # Add all _Add{Mandatory,Optional}Field tests.
1155 AddParametricTests('AddField',
1156 {'is_mandatory': (True, False),
1157 'linebreak': (True, False),
1158 'indent': (0, 1, 2),
1159 'convert': (str, lambda s: s[::-1]),
1160 'is_present': (True, False)})
1161
1162 # Add all _Add{Mandatory,Optional}SubMsg tests.
1163 AddParametricTests('AddSubMsg',
1164 {'is_mandatory': (True, False),
1165 'is_present': (True, False)})
1166
1167 # Add all _CheckManifest() test cases.
1168 AddParametricTests('CheckManifest',
1169 {'fail_mismatched_block_size': (True, False),
1170 'fail_bad_sigs': (True, False),
1171 'fail_mismatched_oki_ori': (True, False),
1172 'fail_bad_oki': (True, False),
1173 'fail_bad_ori': (True, False),
1174 'fail_bad_nki': (True, False),
1175 'fail_bad_nri': (True, False),
Gilad Arnold382df5c2013-05-03 12:49:28 -07001176 'fail_missing_ops': (True, False),
1177 'fail_old_kernel_fs_size': (True, False),
1178 'fail_old_rootfs_fs_size': (True, False),
1179 'fail_new_kernel_fs_size': (True, False),
1180 'fail_new_rootfs_fs_size': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001181
1182 # Add all _CheckOperation() test cases.
1183 AddParametricTests('CheckOperation',
1184 {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
1185 'BSDIFF'),
1186 'is_last': (True, False),
1187 'allow_signature': (True, False),
1188 'allow_unhashed': (True, False),
1189 'fail_src_extents': (True, False),
1190 'fail_dst_extents': (True, False),
1191 'fail_mismatched_data_offset_length': (True, False),
1192 'fail_missing_dst_extents': (True, False),
1193 'fail_src_length': (True, False),
1194 'fail_dst_length': (True, False),
1195 'fail_data_hash': (True, False),
1196 'fail_prev_data_offset': (True, False)},
1197 validate_func=ValidateCheckOperationTest)
1198
1199 # Add all _CheckOperations() test cases.
1200 AddParametricTests('CheckOperations',
1201 {'fail_bad_type': (True, False),
1202 'fail_nonexhaustive_full_update': (True, False)})
1203
1204 # Add all _CheckOperations() test cases.
1205 AddParametricTests('CheckSignatures',
1206 {'fail_empty_sigs_blob': (True, False),
1207 'fail_missing_pseudo_op': (True, False),
1208 'fail_mismatched_pseudo_op': (True, False),
1209 'fail_sig_missing_fields': (True, False),
1210 'fail_unknown_sig_version': (True, False),
1211 'fail_incorrect_sig': (True, False)})
1212
1213 # Add all Run() test cases.
1214 AddParametricTests('Run',
1215 {'fail_wrong_payload_type': (True, False),
1216 'fail_invalid_block_size': (True, False),
1217 'fail_mismatched_block_size': (True, False),
1218 'fail_excess_data': (True, False)})
1219
1220
1221if __name__ == '__main__':
1222 AddAllParametricTests()
1223 unittest.main()