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