blob: b57c2fcf6b8ba254038067e0768eb3aed06d53e7 [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,
Allie Woodb065e132015-04-24 10:20:27 -0700636 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800637 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,
Allie Woodb065e132015-04-24 10:20:27 -0700649 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800650 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,
Allie Woodb065e132015-04-24 10:20:27 -0700664 self.NewExtentList((1, 4), (12, 2), (1024, 127)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800665 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,
Allie Woodb065e132015-04-24 10:20:27 -0700679 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800680 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,
Allie Woodb065e132015-04-24 10:20:27 -0700694 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800695 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,
Allie Woodb065e132015-04-24 10:20:27 -0700702 self.NewExtentList((1, 4), (12, 2), (1024, 129)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800703 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,
Allie Woodb065e132015-04-24 10:20:27 -0700717 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800718 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,
Allie Woodb065e132015-04-24 10:20:27 -0700732 self.NewExtentList((1, 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 testCheckMoveOperation_FailZeroStartBlock(self):
741 """Tests _CheckMoveOperation(); fails, has extent with start block 0."""
742 payload_checker = checker.PayloadChecker(self.MockPayload())
743 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
744 op.type = common.OpType.MOVE
745
746 self.AddToMessage(op.src_extents,
Gilad Arnold5502b562013-03-08 13:22:31 -0800747 self.NewExtentList((0, 4), (12, 2), (1024, 128)))
748 self.AddToMessage(op.dst_extents,
749 self.NewExtentList((8, 128), (512, 6)))
750 self.assertRaises(
751 update_payload.PayloadError,
752 payload_checker._CheckMoveOperation,
753 op, None, 134, 134, 'foo')
754
Allie Woodb065e132015-04-24 10:20:27 -0700755 self.AddToMessage(op.src_extents,
756 self.NewExtentList((1, 4), (12, 2), (1024, 128)))
757 self.AddToMessage(op.dst_extents,
758 self.NewExtentList((0, 128), (512, 6)))
759 self.assertRaises(
760 update_payload.PayloadError,
761 payload_checker._CheckMoveOperation,
762 op, None, 134, 134, 'foo')
763
Gilad Arnold5502b562013-03-08 13:22:31 -0800764 def testCheckBsdiff(self):
765 """Tests _CheckMoveOperation()."""
766 payload_checker = checker.PayloadChecker(self.MockPayload())
767
768 # Pass.
769 self.assertIsNone(
770 payload_checker._CheckBsdiffOperation(10000, 3, 'foo'))
771
772 # Fail, missing data blob.
773 self.assertRaises(
774 update_payload.PayloadError,
775 payload_checker._CheckBsdiffOperation,
776 None, 3, 'foo')
777
778 # Fail, too big of a diff blob (unjustified).
779 self.assertRaises(
780 update_payload.PayloadError,
781 payload_checker._CheckBsdiffOperation,
782 10000, 2, 'foo')
783
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800784 def testCheckSourceCopyOperation_Pass(self):
785 """Tests _CheckSourceCopyOperation(); pass case."""
786 payload_checker = checker.PayloadChecker(self.MockPayload())
787 self.assertIsNone(
788 payload_checker._CheckSourceCopyOperation(None, 134, 134, 'foo'))
789
790 def testCheckSourceCopyOperation_FailContainsData(self):
791 """Tests _CheckSourceCopyOperation(); message contains data."""
792 payload_checker = checker.PayloadChecker(self.MockPayload())
793 self.assertRaises(update_payload.PayloadError,
794 payload_checker._CheckSourceCopyOperation,
795 134, 0, 0, 'foo')
796
797 def testCheckSourceCopyOperation_FailBlockCountsMismatch(self):
798 """Tests _CheckSourceCopyOperation(); src and dst block totals not equal."""
799 payload_checker = checker.PayloadChecker(self.MockPayload())
800 self.assertRaises(update_payload.PayloadError,
801 payload_checker._CheckSourceCopyOperation,
802 None, 0, 1, 'foo')
803
Gilad Arnold5502b562013-03-08 13:22:31 -0800804 def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
805 allow_unhashed, fail_src_extents, fail_dst_extents,
806 fail_mismatched_data_offset_length,
807 fail_missing_dst_extents, fail_src_length,
808 fail_dst_length, fail_data_hash,
Allie Wood7cf9f132015-02-26 14:28:19 -0800809 fail_prev_data_offset, fail_bad_minor_version):
Gilad Arnold5502b562013-03-08 13:22:31 -0800810 """Parametric testing of _CheckOperation().
811
812 Args:
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800813 op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE', 'BSDIFF', 'SOURCE_COPY',
814 or 'SOURCE_BSDIFF'.
Gilad Arnoldcb638912013-06-24 04:57:11 -0700815 is_last: Whether we're testing the last operation in a sequence.
816 allow_signature: Whether we're testing a signature-capable operation.
817 allow_unhashed: Whether we're allowing to not hash the data.
818 fail_src_extents: Tamper with src extents.
819 fail_dst_extents: Tamper with dst extents.
820 fail_mismatched_data_offset_length: Make data_{offset,length}
821 inconsistent.
822 fail_missing_dst_extents: Do not include dst extents.
823 fail_src_length: Make src length inconsistent.
824 fail_dst_length: Make dst length inconsistent.
825 fail_data_hash: Tamper with the data blob hash.
826 fail_prev_data_offset: Make data space uses incontiguous.
Allie Wood7cf9f132015-02-26 14:28:19 -0800827 fail_bad_minor_version: Make minor version incompatible with op.
Gilad Arnold5502b562013-03-08 13:22:31 -0800828 """
829 op_type = _OpTypeByName(op_type_name)
830
831 # Create the test object.
832 payload = self.MockPayload()
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700833 payload_checker = checker.PayloadChecker(payload,
834 allow_unhashed=allow_unhashed)
Gilad Arnold5502b562013-03-08 13:22:31 -0800835 block_size = payload_checker.block_size
836
837 # Create auxiliary arguments.
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700838 old_part_size = test_utils.MiB(4)
839 new_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800840 old_block_counters = array.array(
841 'B', [0] * ((old_part_size + block_size - 1) / block_size))
842 new_block_counters = array.array(
843 'B', [0] * ((new_part_size + block_size - 1) / block_size))
844 prev_data_offset = 1876
845 blob_hash_counts = collections.defaultdict(int)
846
847 # Create the operation object for the test.
848 op = update_metadata_pb2.DeltaArchiveManifest.InstallOperation()
849 op.type = op_type
850
851 total_src_blocks = 0
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800852 if op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
853 common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
Gilad Arnold5502b562013-03-08 13:22:31 -0800854 if fail_src_extents:
855 self.AddToMessage(op.src_extents,
Allie Woodb065e132015-04-24 10:20:27 -0700856 self.NewExtentList((1, 0)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800857 else:
858 self.AddToMessage(op.src_extents,
Allie Woodb065e132015-04-24 10:20:27 -0700859 self.NewExtentList((1, 16)))
Gilad Arnold5502b562013-03-08 13:22:31 -0800860 total_src_blocks = 16
861
Allie Wood7cf9f132015-02-26 14:28:19 -0800862 if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
863 payload.manifest.minor_version = 0
864 elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
865 payload.manifest.minor_version = 2 if fail_bad_minor_version else 1
866 elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
867 payload.manifest.minor_version = 1 if fail_bad_minor_version else 2
868
Allie Woodf5c4f3e2015-02-20 16:57:46 -0800869 if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
Gilad Arnold5502b562013-03-08 13:22:31 -0800870 if not fail_mismatched_data_offset_length:
871 op.data_length = 16 * block_size - 8
872 if fail_prev_data_offset:
873 op.data_offset = prev_data_offset + 16
874 else:
875 op.data_offset = prev_data_offset
876
877 fake_data = 'fake-data'.ljust(op.data_length)
878 if not (allow_unhashed or (is_last and allow_signature and
879 op_type == common.OpType.REPLACE)):
880 if not fail_data_hash:
881 # Create a valid data blob hash.
882 op.data_sha256_hash = hashlib.sha256(fake_data).digest()
883 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
884 fake_data)
885 elif fail_data_hash:
886 # Create an invalid data blob hash.
887 op.data_sha256_hash = hashlib.sha256(
888 fake_data.replace(' ', '-')).digest()
889 payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
890 fake_data)
891
892 total_dst_blocks = 0
893 if not fail_missing_dst_extents:
894 total_dst_blocks = 16
895 if fail_dst_extents:
896 self.AddToMessage(op.dst_extents,
897 self.NewExtentList((4, 16), (32, 0)))
898 else:
899 self.AddToMessage(op.dst_extents,
900 self.NewExtentList((4, 8), (64, 8)))
901
902 if total_src_blocks:
903 if fail_src_length:
904 op.src_length = total_src_blocks * block_size + 8
905 else:
906 op.src_length = total_src_blocks * block_size
907 elif fail_src_length:
908 # Add an orphaned src_length.
909 op.src_length = 16
910
911 if total_dst_blocks:
912 if fail_dst_length:
913 op.dst_length = total_dst_blocks * block_size + 8
914 else:
915 op.dst_length = total_dst_blocks * block_size
916
917 self.mox.ReplayAll()
918 should_fail = (fail_src_extents or fail_dst_extents or
919 fail_mismatched_data_offset_length or
920 fail_missing_dst_extents or fail_src_length or
Allie Wood7cf9f132015-02-26 14:28:19 -0800921 fail_dst_length or fail_data_hash or fail_prev_data_offset or
922 fail_bad_minor_version)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700923 args = (op, 'foo', is_last, old_block_counters, new_block_counters,
924 old_part_size, new_part_size, prev_data_offset, allow_signature,
925 blob_hash_counts)
Gilad Arnold5502b562013-03-08 13:22:31 -0800926 if should_fail:
927 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700928 payload_checker._CheckOperation, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800929 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700930 self.assertEqual(op.data_length if op.HasField('data_length') else 0,
931 payload_checker._CheckOperation(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800932
933 def testAllocBlockCounters(self):
934 """Tests _CheckMoveOperation()."""
935 payload_checker = checker.PayloadChecker(self.MockPayload())
936 block_size = payload_checker.block_size
937
938 # Check allocation for block-aligned partition size, ensure it's integers.
939 result = payload_checker._AllocBlockCounters(16 * block_size)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700940 self.assertEqual(16, len(result))
941 self.assertEqual(int, type(result[0]))
Gilad Arnold5502b562013-03-08 13:22:31 -0800942
943 # Check allocation of unaligned partition sizes.
944 result = payload_checker._AllocBlockCounters(16 * block_size - 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700945 self.assertEqual(16, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800946 result = payload_checker._AllocBlockCounters(16 * block_size + 1)
Gilad Arnoldcb638912013-06-24 04:57:11 -0700947 self.assertEqual(17, len(result))
Gilad Arnold5502b562013-03-08 13:22:31 -0800948
Allie Woodfb04d302015-04-03 14:25:48 -0700949 def DoCheckOperationsTest(self, fail_nonexhaustive_full_update):
Gilad Arnold5502b562013-03-08 13:22:31 -0800950 # Generate a test payload. For this test, we only care about one
951 # (arbitrary) set of operations, so we'll only be generating kernel and
952 # test with them.
953 payload_gen = test_utils.PayloadGenerator()
954
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700955 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800956 payload_gen.SetBlockSize(block_size)
957
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700958 rootfs_part_size = test_utils.MiB(8)
Gilad Arnold5502b562013-03-08 13:22:31 -0800959
960 # Fake rootfs operations in a full update, tampered with as required.
961 rootfs_op_type = common.OpType.REPLACE
Gilad Arnold5502b562013-03-08 13:22:31 -0800962 rootfs_data_length = rootfs_part_size
963 if fail_nonexhaustive_full_update:
964 rootfs_data_length -= block_size
965
966 payload_gen.AddOperation(False, rootfs_op_type,
967 dst_extents=[(0, rootfs_data_length / block_size)],
968 data_offset=0,
969 data_length=rootfs_data_length)
970
971 # Create the test object.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700972 payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
973 checker_init_dargs={
974 'allow_unhashed': True})
Gilad Arnold5502b562013-03-08 13:22:31 -0800975 payload_checker.payload_type = checker._TYPE_FULL
976 report = checker._PayloadReport()
977
Gilad Arnoldcb638912013-06-24 04:57:11 -0700978 args = (payload_checker.payload.manifest.install_operations, report,
979 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
Allie Woodfb04d302015-04-03 14:25:48 -0700980 if fail_nonexhaustive_full_update:
Gilad Arnold5502b562013-03-08 13:22:31 -0800981 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -0700982 payload_checker._CheckOperations, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -0800983 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -0700984 self.assertEqual(rootfs_data_length,
985 payload_checker._CheckOperations(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -0800986
987 def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
988 fail_mismatched_pseudo_op, fail_sig_missing_fields,
989 fail_unknown_sig_version, fail_incorrect_sig):
990 # Generate a test payload. For this test, we only care about the signature
991 # block and how it relates to the payload hash. Therefore, we're generating
992 # a random (otherwise useless) payload for this purpose.
993 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700994 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -0800995 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -0700996 rootfs_part_size = test_utils.MiB(2)
997 kernel_part_size = test_utils.KiB(16)
Gilad Arnold5502b562013-03-08 13:22:31 -0800998 payload_gen.SetPartInfo(False, True, rootfs_part_size,
999 hashlib.sha256('fake-new-rootfs-content').digest())
Gilad Arnold382df5c2013-05-03 12:49:28 -07001000 payload_gen.SetPartInfo(True, True, kernel_part_size,
Gilad Arnold5502b562013-03-08 13:22:31 -08001001 hashlib.sha256('fake-new-kernel-content').digest())
1002 payload_gen.AddOperationWithData(
1003 False, common.OpType.REPLACE,
1004 dst_extents=[(0, rootfs_part_size / block_size)],
1005 data_blob=os.urandom(rootfs_part_size))
1006
1007 do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
1008 do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
1009 fail_sig_missing_fields or fail_unknown_sig_version
1010 or fail_incorrect_sig)
1011
1012 sigs_data = None
1013 if do_forge_sigs_data:
1014 sigs_gen = test_utils.SignaturesGenerator()
1015 if not fail_empty_sigs_blob:
1016 if fail_sig_missing_fields:
1017 sig_data = None
1018 else:
1019 sig_data = test_utils.SignSha256('fake-payload-content',
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001020 test_utils._PRIVKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001021 sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
1022
1023 sigs_data = sigs_gen.ToBinary()
1024 payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
1025
1026 if do_forge_pseudo_op:
1027 assert sigs_data is not None, 'should have forged signatures blob by now'
1028 sigs_len = len(sigs_data)
1029 payload_gen.AddOperation(
1030 False, common.OpType.REPLACE,
1031 data_offset=payload_gen.curr_offset / 2,
1032 data_length=sigs_len / 2,
1033 dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
1034
1035 # Generate payload (complete w/ signature) and create the test object.
1036 payload_checker = _GetPayloadChecker(
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001037 payload_gen.WriteToFileWithData,
1038 payload_gen_dargs={
1039 'sigs_data': sigs_data,
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001040 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001041 'do_add_pseudo_operation': not do_forge_pseudo_op})
Gilad Arnold5502b562013-03-08 13:22:31 -08001042 payload_checker.payload_type = checker._TYPE_FULL
1043 report = checker._PayloadReport()
1044
1045 # We have to check the manifest first in order to set signature attributes.
Gilad Arnold382df5c2013-05-03 12:49:28 -07001046 payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
Gilad Arnold5502b562013-03-08 13:22:31 -08001047
1048 should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
1049 fail_mismatched_pseudo_op or fail_sig_missing_fields or
1050 fail_unknown_sig_version or fail_incorrect_sig)
Gilad Arnoldcb638912013-06-24 04:57:11 -07001051 args = (report, test_utils._PUBKEY_FILE_NAME)
Gilad Arnold5502b562013-03-08 13:22:31 -08001052 if should_fail:
1053 self.assertRaises(update_payload.PayloadError,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001054 payload_checker._CheckSignatures, *args)
Gilad Arnold5502b562013-03-08 13:22:31 -08001055 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001056 self.assertIsNone(payload_checker._CheckSignatures(*args))
Gilad Arnold5502b562013-03-08 13:22:31 -08001057
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001058 def DoCheckMinorVersionTest(self, minor_version, payload_type):
1059 """Parametric testing for CheckMinorVersion().
1060
1061 Args:
1062 minor_version: The payload minor version to test with.
1063 payload_type: The type of the payload we're testing, delta or full.
1064 """
1065 # Create the test object.
1066 payload_checker = checker.PayloadChecker(self.MockPayload())
1067 report = checker._PayloadReport()
1068
1069 should_succeed = (
1070 (minor_version == 0 and payload_type == checker._TYPE_FULL) or
1071 (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
1072 (minor_version == 2 and payload_type == checker._TYPE_DELTA))
1073 args = (report, minor_version, payload_type)
1074
1075 if should_succeed:
1076 self.assertIsNone(payload_checker._CheckMinorVersion(*args))
1077 else:
1078 self.assertRaises(update_payload.PayloadError,
1079 payload_checker._CheckMinorVersion, *args)
1080
Gilad Arnold5502b562013-03-08 13:22:31 -08001081 def DoRunTest(self, fail_wrong_payload_type, fail_invalid_block_size,
1082 fail_mismatched_block_size, fail_excess_data):
1083 # Generate a test payload. For this test, we generate a full update that
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001084 # has sample kernel and rootfs operations. Since most testing is done with
Gilad Arnold5502b562013-03-08 13:22:31 -08001085 # internal PayloadChecker methods that are tested elsewhere, here we only
1086 # tamper with what's actually being manipulated and/or tested in the Run()
1087 # method itself. Note that the checker doesn't verify partition hashes, so
1088 # they're safe to fake.
1089 payload_gen = test_utils.EnhancedPayloadGenerator()
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001090 block_size = test_utils.KiB(4)
Gilad Arnold5502b562013-03-08 13:22:31 -08001091 payload_gen.SetBlockSize(block_size)
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001092 kernel_part_size = test_utils.KiB(16)
1093 rootfs_part_size = test_utils.MiB(2)
Gilad Arnold5502b562013-03-08 13:22:31 -08001094 payload_gen.SetPartInfo(False, True, rootfs_part_size,
1095 hashlib.sha256('fake-new-rootfs-content').digest())
1096 payload_gen.SetPartInfo(True, True, kernel_part_size,
1097 hashlib.sha256('fake-new-kernel-content').digest())
1098 payload_gen.AddOperationWithData(
1099 False, common.OpType.REPLACE,
1100 dst_extents=[(0, rootfs_part_size / block_size)],
1101 data_blob=os.urandom(rootfs_part_size))
1102 payload_gen.AddOperationWithData(
1103 True, common.OpType.REPLACE,
1104 dst_extents=[(0, kernel_part_size / block_size)],
1105 data_blob=os.urandom(kernel_part_size))
1106
1107 # Generate payload (complete w/ signature) and create the test object.
Gilad Arnold5502b562013-03-08 13:22:31 -08001108 if fail_invalid_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001109 use_block_size = block_size + 5 # Not a power of two.
Gilad Arnold5502b562013-03-08 13:22:31 -08001110 elif fail_mismatched_block_size:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001111 use_block_size = block_size * 2 # Different that payload stated.
Gilad Arnold5502b562013-03-08 13:22:31 -08001112 else:
1113 use_block_size = block_size
Gilad Arnold5502b562013-03-08 13:22:31 -08001114
Gilad Arnoldcb638912013-06-24 04:57:11 -07001115 kwargs = {
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001116 'payload_gen_dargs': {
Gilad Arnold18f4f9f2013-04-02 16:24:41 -07001117 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001118 'do_add_pseudo_operation': True,
1119 'is_pseudo_in_kernel': True,
1120 'padding': os.urandom(1024) if fail_excess_data else None},
1121 'checker_init_dargs': {
1122 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
1123 'block_size': use_block_size}}
1124 if fail_invalid_block_size:
1125 self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001126 payload_gen.WriteToFileWithData, **kwargs)
Gilad Arnold5502b562013-03-08 13:22:31 -08001127 else:
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001128 payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
Gilad Arnoldcb638912013-06-24 04:57:11 -07001129 **kwargs)
1130 kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME}
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001131 should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
1132 fail_excess_data)
1133 if should_fail:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001134 self.assertRaises(update_payload.PayloadError, payload_checker.Run,
1135 **kwargs)
Gilad Arnoldeaed0d12013-04-30 15:38:22 -07001136 else:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001137 self.assertIsNone(payload_checker.Run(**kwargs))
Gilad Arnold5502b562013-03-08 13:22:31 -08001138
Gilad Arnold5502b562013-03-08 13:22:31 -08001139# This implements a generic API, hence the occasional unused args.
1140# pylint: disable=W0613
1141def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
1142 allow_unhashed, fail_src_extents,
1143 fail_dst_extents,
1144 fail_mismatched_data_offset_length,
1145 fail_missing_dst_extents, fail_src_length,
1146 fail_dst_length, fail_data_hash,
Allie Wood7cf9f132015-02-26 14:28:19 -08001147 fail_prev_data_offset, fail_bad_minor_version):
Gilad Arnold5502b562013-03-08 13:22:31 -08001148 """Returns True iff the combination of arguments represents a valid test."""
1149 op_type = _OpTypeByName(op_type_name)
1150
Allie Wood7cf9f132015-02-26 14:28:19 -08001151 # REPLACE/REPLACE_BZ operations don't read data from src partition. They are
1152 # compatible with all valid minor versions, so we don't need to check that.
Gilad Arnold5502b562013-03-08 13:22:31 -08001153 if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
Allie Wood7cf9f132015-02-26 14:28:19 -08001154 fail_src_extents or fail_src_length or fail_bad_minor_version)):
Gilad Arnold5502b562013-03-08 13:22:31 -08001155 return False
1156
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001157 # MOVE and SOURCE_COPY operations don't carry data.
1158 if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and (
Gilad Arnold5502b562013-03-08 13:22:31 -08001159 fail_mismatched_data_offset_length or fail_data_hash or
1160 fail_prev_data_offset)):
1161 return False
1162
1163 return True
1164
1165
1166def TestMethodBody(run_method_name, run_dargs):
1167 """Returns a function that invokes a named method with named arguments."""
1168 return lambda self: getattr(self, run_method_name)(**run_dargs)
1169
1170
1171def AddParametricTests(tested_method_name, arg_space, validate_func=None):
1172 """Enumerates and adds specific parametric tests to PayloadCheckerTest.
1173
1174 This function enumerates a space of test parameters (defined by arg_space),
1175 then binds a new, unique method name in PayloadCheckerTest to a test function
1176 that gets handed the said parameters. This is a preferable approach to doing
1177 the enumeration and invocation during the tests because this way each test is
1178 treated as a complete run by the unittest framework, and so benefits from the
1179 usual setUp/tearDown mechanics.
1180
1181 Args:
Gilad Arnoldcb638912013-06-24 04:57:11 -07001182 tested_method_name: Name of the tested PayloadChecker method.
1183 arg_space: A dictionary containing variables (keys) and lists of values
1184 (values) associated with them.
1185 validate_func: A function used for validating test argument combinations.
Gilad Arnold5502b562013-03-08 13:22:31 -08001186 """
1187 for value_tuple in itertools.product(*arg_space.itervalues()):
1188 run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
1189 if validate_func and not validate_func(**run_dargs):
1190 continue
1191 run_method_name = 'Do%sTest' % tested_method_name
1192 test_method_name = 'test%s' % tested_method_name
1193 for arg_key, arg_val in run_dargs.iteritems():
1194 if arg_val or type(arg_val) is int:
1195 test_method_name += '__%s=%s' % (arg_key, arg_val)
1196 setattr(PayloadCheckerTest, test_method_name,
1197 TestMethodBody(run_method_name, run_dargs))
1198
1199
1200def AddAllParametricTests():
1201 """Enumerates and adds all parametric tests to PayloadCheckerTest."""
1202 # Add all _CheckElem() test cases.
1203 AddParametricTests('AddElem',
1204 {'linebreak': (True, False),
1205 'indent': (0, 1, 2),
1206 'convert': (str, lambda s: s[::-1]),
1207 'is_present': (True, False),
1208 'is_mandatory': (True, False),
1209 'is_submsg': (True, False)})
1210
1211 # Add all _Add{Mandatory,Optional}Field tests.
1212 AddParametricTests('AddField',
1213 {'is_mandatory': (True, False),
1214 'linebreak': (True, False),
1215 'indent': (0, 1, 2),
1216 'convert': (str, lambda s: s[::-1]),
1217 'is_present': (True, False)})
1218
1219 # Add all _Add{Mandatory,Optional}SubMsg tests.
1220 AddParametricTests('AddSubMsg',
1221 {'is_mandatory': (True, False),
1222 'is_present': (True, False)})
1223
1224 # Add all _CheckManifest() test cases.
1225 AddParametricTests('CheckManifest',
1226 {'fail_mismatched_block_size': (True, False),
1227 'fail_bad_sigs': (True, False),
1228 'fail_mismatched_oki_ori': (True, False),
1229 'fail_bad_oki': (True, False),
1230 'fail_bad_ori': (True, False),
1231 'fail_bad_nki': (True, False),
1232 'fail_bad_nri': (True, False),
Gilad Arnold382df5c2013-05-03 12:49:28 -07001233 'fail_old_kernel_fs_size': (True, False),
1234 'fail_old_rootfs_fs_size': (True, False),
1235 'fail_new_kernel_fs_size': (True, False),
1236 'fail_new_rootfs_fs_size': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001237
1238 # Add all _CheckOperation() test cases.
1239 AddParametricTests('CheckOperation',
1240 {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001241 'BSDIFF', 'SOURCE_COPY',
1242 'SOURCE_BSDIFF'),
Gilad Arnold5502b562013-03-08 13:22:31 -08001243 'is_last': (True, False),
1244 'allow_signature': (True, False),
1245 'allow_unhashed': (True, False),
1246 'fail_src_extents': (True, False),
1247 'fail_dst_extents': (True, False),
1248 'fail_mismatched_data_offset_length': (True, False),
1249 'fail_missing_dst_extents': (True, False),
1250 'fail_src_length': (True, False),
1251 'fail_dst_length': (True, False),
1252 'fail_data_hash': (True, False),
Allie Wood7cf9f132015-02-26 14:28:19 -08001253 'fail_prev_data_offset': (True, False),
1254 'fail_bad_minor_version': (True, False)},
Gilad Arnold5502b562013-03-08 13:22:31 -08001255 validate_func=ValidateCheckOperationTest)
1256
1257 # Add all _CheckOperations() test cases.
1258 AddParametricTests('CheckOperations',
Allie Woodfb04d302015-04-03 14:25:48 -07001259 {'fail_nonexhaustive_full_update': (True, False)})
Gilad Arnold5502b562013-03-08 13:22:31 -08001260
1261 # Add all _CheckOperations() test cases.
1262 AddParametricTests('CheckSignatures',
1263 {'fail_empty_sigs_blob': (True, False),
1264 'fail_missing_pseudo_op': (True, False),
1265 'fail_mismatched_pseudo_op': (True, False),
1266 'fail_sig_missing_fields': (True, False),
1267 'fail_unknown_sig_version': (True, False),
1268 'fail_incorrect_sig': (True, False)})
1269
Allie Woodf5c4f3e2015-02-20 16:57:46 -08001270 # Add all _CheckMinorVersion() test cases.
1271 AddParametricTests('CheckMinorVersion',
1272 {'minor_version': (0, 1, 2, 555),
1273 'payload_type': (checker._TYPE_FULL,
1274 checker._TYPE_DELTA)})
1275
Gilad Arnold5502b562013-03-08 13:22:31 -08001276 # Add all Run() test cases.
1277 AddParametricTests('Run',
1278 {'fail_wrong_payload_type': (True, False),
1279 'fail_invalid_block_size': (True, False),
1280 'fail_mismatched_block_size': (True, False),
1281 'fail_excess_data': (True, False)})
1282
1283
1284if __name__ == '__main__':
1285 AddAllParametricTests()
1286 unittest.main()