blob: eb8b56446018e5a9e3a988d9715a2d362303f29a [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
Allie Woodfb04d302015-04-03 14:25:48 -0700925 def DoCheckOperationsTest(self, fail_nonexhaustive_full_update):
Gilad Arnold5502b562013-03-08 13:22:31 -0800926 # Generate a test payload. For this test, we only care about one
927 # (arbitrary) set of operations, so we'll only be generating kernel and
928 # test with them.
929 payload_gen = test_utils.PayloadGenerator()
930
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700931 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800932 payload_gen.SetBlockSize(block_size)
933
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700934 rootfs_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800935
936 # Fake rootfs operations in a full update, tampered with as required.
937 rootfs_op_type = common.OpType.REPLACE
Gilad Arnold5502b562013-03-08 13:22:31 -0800938 rootfs_data_length = rootfs_part_size
939 if fail_nonexhaustive_full_update:
940 rootfs_data_length -= block_size
941
942 payload_gen.AddOperation(False, rootfs_op_type,
943 dst_extents=[(0, rootfs_data_length / block_size)],
944 data_offset=0,
945 data_length=rootfs_data_length)
946
947 # Create the test object.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700948 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
949 checker_init_dargs={
950 'allow_unhashed': True})
Gilad Arnold5502b562013-03-08 13:22:31 -0800951 payload_checker.payload_type = checker._TYPE_FULL
952 report = checker._PayloadReport()
953
Gilad Arnoldcb638912013-06-24 04:57:11 -0700954 args = (payload_checker.payload.manifest.install_operations, report,
955 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
Allie Woodfb04d302015-04-03 14:25:48 -0700956 if fail_nonexhaustive_full_update:
Gilad Arnold5502b562013-03-08 13:22:31 -0800957 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700958 payload_checker._CheckOperations, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800959 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700960 self.assertEqual(rootfs_data_length,
961 payload_checker._CheckOperations(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800962
963 def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
964 fail_mismatched_pseudo_op, fail_sig_missing_fields,
965 fail_unknown_sig_version, fail_incorrect_sig):
966 # Generate a test payload. For this test, we only care about the signature
967 # block and how it relates to the payload hash. Therefore, we're generating
968 # a random (otherwise useless) payload for this purpose.
969 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700970 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800971 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700972 rootfs_part_size = test_utils.MiB(2)
973 kernel_part_size = test_utils.KiB(16)
Gilad Arnold5502b562013-03-08 13:22:31 -0800974 payload_gen.SetPartInfo(False, True, rootfs_part_size,
975 hashlib.sha256('fake-new-rootfs-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -0700976 payload_gen.SetPartInfo(True, True, kernel_part_size,
Gilad Arnold5502b562013-03-08 13:22:31 -0800977 hashlib.sha256('fake-new-kernel-content').digest())
978 payload_gen.AddOperationWithData(
979 False, common.OpType.REPLACE,
980 dst_extents=[(0, rootfs_part_size / block_size)],
981 data_blob=os.urandom(rootfs_part_size))
982
983 do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
984 do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
985 fail_sig_missing_fields or fail_unknown_sig_version
986 or fail_incorrect_sig)
987
988 sigs_data = None
989 if do_forge_sigs_data:
990 sigs_gen = test_utils.SignaturesGenerator()
991 if not fail_empty_sigs_blob:
992 if fail_sig_missing_fields:
993 sig_data = None
994 else:
995 sig_data = test_utils.SignSha256('fake-payload-content',
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700996 test_utils._PRIVKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -0800997 sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
998
999 sigs_data = sigs_gen.ToBinary()
1000 payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
1001
1002 if do_forge_pseudo_op:
1003 assert sigs_data is not None, 'should have forged signatures blob by now'
1004 sigs_len = len(sigs_data)
1005 payload_gen.AddOperation(
1006 False, common.OpType.REPLACE,
1007 data_offset=payload_gen.curr_offset / 2,
1008 data_length=sigs_len / 2,
1009 dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
1010
1011 # Generate payload (complete w/ signature) and create the test object.
1012 payload_checker = _GetPayloadChecker(
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001013 payload_gen.WriteToFileWithData,
1014 payload_gen_dargs={
1015 'sigs_data': sigs_data,
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001016 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001017 'do_add_pseudo_operation': not do_forge_pseudo_op})
Gilad Arnold5502b562013-03-08 13:22:31 -08001018 payload_checker.payload_type = checker._TYPE_FULL
1019 report = checker._PayloadReport()
1020
1021 # We have to check the manifest first in order to set signature attributes.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001022 payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -08001023
1024 should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
1025 fail_mismatched_pseudo_op or fail_sig_missing_fields or
1026 fail_unknown_sig_version or fail_incorrect_sig)
Gilad Arnoldcb638912013-06-24 04:57:11 -07001027 args = (report, test_utils._PUBKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001028 if should_fail:
1029 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001030 payload_checker._CheckSignatures, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -08001031 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001032 self.assertIsNone(payload_checker._CheckSignatures(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -08001033
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001034 def DoCheckMinorVersionTest(self, minor_version, payload_type):
1035 """Parametric testing for CheckMinorVersion().
1036
1037 Args:
1038 minor_version: The payload minor version to test with.
1039 payload_type: The type of the payload we're testing, delta or full.
1040 """
1041 # Create the test object.
1042 payload_checker = checker.PayloadChecker(self.MockPayload())
1043 report = checker._PayloadReport()
1044
1045 should_succeed = (
1046 (minor_version == 0 and payload_type == checker._TYPE_FULL) or
1047 (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
1048 (minor_version == 2 and payload_type == checker._TYPE_DELTA))
1049 args = (report, minor_version, payload_type)
1050
1051 if should_succeed:
1052 self.assertIsNone(payload_checker._CheckMinorVersion(*args))
1053 else:
1054 self.assertRaises(update_payload.PayloadError,
1055 payload_checker._CheckMinorVersion, *args)
1056
Gilad Arnold5502b562013-03-08 13:22:31 -08001057 def DoRunTest(self, fail_wrong_payload_type, fail_invalid_block_size,
1058 fail_mismatched_block_size, fail_excess_data):
1059 # Generate a test payload. For this test, we generate a full update that
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001060 # has sample kernel and rootfs operations. Since most testing is done with
Gilad Arnold5502b562013-03-08 13:22:31 -08001061 # internal PayloadChecker methods that are tested elsewhere, here we only
1062 # tamper with what's actually being manipulated and/or tested in the Run()
1063 # method itself. Note that the checker doesn't verify partition hashes, so
1064 # they're safe to fake.
1065 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001066 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001067 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001068 kernel_part_size = test_utils.KiB(16)
1069 rootfs_part_size = test_utils.MiB(2)
Gilad Arnold5502b562013-03-08 13:22:31 -08001070 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1071 hashlib.sha256('fake-new-rootfs-content').digest())
1072 payload_gen.SetPartInfo(True, True, kernel_part_size,
1073 hashlib.sha256('fake-new-kernel-content').digest())
1074 payload_gen.AddOperationWithData(
1075 False, common.OpType.REPLACE,
1076 dst_extents=[(0, rootfs_part_size / block_size)],
1077 data_blob=os.urandom(rootfs_part_size))
1078 payload_gen.AddOperationWithData(
1079 True, common.OpType.REPLACE,
1080 dst_extents=[(0, kernel_part_size / block_size)],
1081 data_blob=os.urandom(kernel_part_size))
1082
1083 # Generate payload (complete w/ signature) and create the test object.
Gilad Arnold5502b562013-03-08 13:22:31 -08001084 if fail_invalid_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001085 use_block_size = block_size + 5 # Not a power of two.
Gilad Arnold5502b562013-03-08 13:22:31 -08001086 elif fail_mismatched_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001087 use_block_size = block_size * 2 # Different that payload stated.
Gilad Arnold5502b562013-03-08 13:22:31 -08001088 else:
1089 use_block_size = block_size
Gilad Arnold5502b562013-03-08 13:22:31 -08001090
Gilad Arnoldcb638912013-06-24 04:57:11 -07001091 kwargs = {
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001092 'payload_gen_dargs': {
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001093 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001094 'do_add_pseudo_operation': True,
1095 'is_pseudo_in_kernel': True,
1096 'padding': os.urandom(1024) if fail_excess_data else None},
1097 'checker_init_dargs': {
1098 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
1099 'block_size': use_block_size}}
1100 if fail_invalid_block_size:
1101 self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001102 payload_gen.WriteToFileWithData, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -08001103 else:
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001104 payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001105 **kwargs)
1106 kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME}
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001107 should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
1108 fail_excess_data)
1109 if should_fail:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001110 self.assertRaises(update_payload.PayloadError, payload_checker.Run,
1111 **kwargs)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001112 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001113 self.assertIsNone(payload_checker.Run(**kwargs))
Gilad Arnold5502b562013-03-08 13:22:31 -08001114
Gilad Arnold5502b562013-03-08 13:22:31 -08001115# This implements a generic API, hence the occasional unused args.
1116# pylint: disable=W0613
1117def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
1118 allow_unhashed, fail_src_extents,
1119 fail_dst_extents,
1120 fail_mismatched_data_offset_length,
1121 fail_missing_dst_extents, fail_src_length,
1122 fail_dst_length, fail_data_hash,
Allie Wood7cf9f132015-02-26 14:28:19 -08001123 fail_prev_data_offset, fail_bad_minor_version):
Gilad Arnold5502b562013-03-08 13:22:31 -08001124 """Returns True iff the combination of arguments represents a valid test."""
1125 op_type = _OpTypeByName(op_type_name)
1126
Allie Wood7cf9f132015-02-26 14:28:19 -08001127 # REPLACE/REPLACE_BZ operations don't read data from src partition. They are
1128 # compatible with all valid minor versions, so we don't need to check that.
Gilad Arnold5502b562013-03-08 13:22:31 -08001129 if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
Allie Wood7cf9f132015-02-26 14:28:19 -08001130 fail_src_extents or fail_src_length or fail_bad_minor_version)):
Gilad Arnold5502b562013-03-08 13:22:31 -08001131 return False
1132
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001133 # MOVE and SOURCE_COPY operations don't carry data.
1134 if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and (
Gilad Arnold5502b562013-03-08 13:22:31 -08001135 fail_mismatched_data_offset_length or fail_data_hash or
1136 fail_prev_data_offset)):
1137 return False
1138
1139 return True
1140
1141
1142def TestMethodBody(run_method_name, run_dargs):
1143 """Returns a function that invokes a named method with named arguments."""
1144 return lambda self: getattr(self, run_method_name)(**run_dargs)
1145
1146
1147def AddParametricTests(tested_method_name, arg_space, validate_func=None):
1148 """Enumerates and adds specific parametric tests to PayloadCheckerTest.
1149
1150 This function enumerates a space of test parameters (defined by arg_space),
1151 then binds a new, unique method name in PayloadCheckerTest to a test function
1152 that gets handed the said parameters. This is a preferable approach to doing
1153 the enumeration and invocation during the tests because this way each test is
1154 treated as a complete run by the unittest framework, and so benefits from the
1155 usual setUp/tearDown mechanics.
1156
1157 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001158 tested_method_name: Name of the tested PayloadChecker method.
1159 arg_space: A dictionary containing variables (keys) and lists of values
1160 (values) associated with them.
1161 validate_func: A function used for validating test argument combinations.
Gilad Arnold5502b562013-03-08 13:22:31 -08001162 """
1163 for value_tuple in itertools.product(*arg_space.itervalues()):
1164 run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
1165 if validate_func and not validate_func(**run_dargs):
1166 continue
1167 run_method_name = 'Do%sTest' % tested_method_name
1168 test_method_name = 'test%s' % tested_method_name
1169 for arg_key, arg_val in run_dargs.iteritems():
1170 if arg_val or type(arg_val) is int:
1171 test_method_name += '__%s=%s' % (arg_key, arg_val)
1172 setattr(PayloadCheckerTest, test_method_name,
1173 TestMethodBody(run_method_name, run_dargs))
1174
1175
1176def AddAllParametricTests():
1177 """Enumerates and adds all parametric tests to PayloadCheckerTest."""
1178 # Add all _CheckElem() test cases.
1179 AddParametricTests('AddElem',
1180 {'linebreak': (True, False),
1181 'indent': (0, 1, 2),
1182 'convert': (str, lambda s: s[::-1]),
1183 'is_present': (True, False),
1184 'is_mandatory': (True, False),
1185 'is_submsg': (True, False)})
1186
1187 # Add all _Add{Mandatory,Optional}Field tests.
1188 AddParametricTests('AddField',
1189 {'is_mandatory': (True, False),
1190 'linebreak': (True, False),
1191 'indent': (0, 1, 2),
1192 'convert': (str, lambda s: s[::-1]),
1193 'is_present': (True, False)})
1194
1195 # Add all _Add{Mandatory,Optional}SubMsg tests.
1196 AddParametricTests('AddSubMsg',
1197 {'is_mandatory': (True, False),
1198 'is_present': (True, False)})
1199
1200 # Add all _CheckManifest() test cases.
1201 AddParametricTests('CheckManifest',
1202 {'fail_mismatched_block_size': (True, False),
1203 'fail_bad_sigs': (True, False),
1204 'fail_mismatched_oki_ori': (True, False),
1205 'fail_bad_oki': (True, False),
1206 'fail_bad_ori': (True, False),
1207 'fail_bad_nki': (True, False),
1208 'fail_bad_nri': (True, False),
Gilad Arnold382df5c2013-05-03 12:49:28 -07001209 'fail_old_kernel_fs_size': (True, False),
1210 'fail_old_rootfs_fs_size': (True, False),
1211 'fail_new_kernel_fs_size': (True, False),
1212 'fail_new_rootfs_fs_size': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001213
1214 # Add all _CheckOperation() test cases.
1215 AddParametricTests('CheckOperation',
1216 {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001217 'BSDIFF', 'SOURCE_COPY',
1218 'SOURCE_BSDIFF'),
Gilad Arnold5502b562013-03-08 13:22:31 -08001219 'is_last': (True, False),
1220 'allow_signature': (True, False),
1221 'allow_unhashed': (True, False),
1222 'fail_src_extents': (True, False),
1223 'fail_dst_extents': (True, False),
1224 'fail_mismatched_data_offset_length': (True, False),
1225 'fail_missing_dst_extents': (True, False),
1226 'fail_src_length': (True, False),
1227 'fail_dst_length': (True, False),
1228 'fail_data_hash': (True, False),
Allie Wood7cf9f132015-02-26 14:28:19 -08001229 'fail_prev_data_offset': (True, False),
1230 'fail_bad_minor_version': (True, False)},
Gilad Arnold5502b562013-03-08 13:22:31 -08001231 validate_func=ValidateCheckOperationTest)
1232
1233 # Add all _CheckOperations() test cases.
1234 AddParametricTests('CheckOperations',
Allie Woodfb04d302015-04-03 14:25:48 -07001235 {'fail_nonexhaustive_full_update': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001236
1237 # Add all _CheckOperations() test cases.
1238 AddParametricTests('CheckSignatures',
1239 {'fail_empty_sigs_blob': (True, False),
1240 'fail_missing_pseudo_op': (True, False),
1241 'fail_mismatched_pseudo_op': (True, False),
1242 'fail_sig_missing_fields': (True, False),
1243 'fail_unknown_sig_version': (True, False),
1244 'fail_incorrect_sig': (True, False)})
1245
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001246 # Add all _CheckMinorVersion() test cases.
1247 AddParametricTests('CheckMinorVersion',
1248 {'minor_version': (0, 1, 2, 555),
1249 'payload_type': (checker._TYPE_FULL,
1250 checker._TYPE_DELTA)})
1251
Gilad Arnold5502b562013-03-08 13:22:31 -08001252 # Add all Run() test cases.
1253 AddParametricTests('Run',
1254 {'fail_wrong_payload_type': (True, False),
1255 'fail_invalid_block_size': (True, False),
1256 'fail_mismatched_block_size': (True, False),
1257 'fail_excess_data': (True, False)})
1258
1259
1260if __name__ == '__main__':
1261 AddAllParametricTests()
1262 unittest.main()