xref: /aosp_15_r20/system/apex/apexer/apexer_test.py (revision 33f3758387333dbd2962d7edbd98681940d895da)
1*33f37583SAndroid Build Coastguard Worker#!/usr/bin/env python
2*33f37583SAndroid Build Coastguard Worker#
3*33f37583SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project
4*33f37583SAndroid Build Coastguard Worker#
5*33f37583SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*33f37583SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*33f37583SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*33f37583SAndroid Build Coastguard Worker#
9*33f37583SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*33f37583SAndroid Build Coastguard Worker#
11*33f37583SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*33f37583SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*33f37583SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*33f37583SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*33f37583SAndroid Build Coastguard Worker# limitations under the License.
16*33f37583SAndroid Build Coastguard Worker#
17*33f37583SAndroid Build Coastguard Worker"""Unit tests for apexer."""
18*33f37583SAndroid Build Coastguard Worker
19*33f37583SAndroid Build Coastguard Workerimport hashlib
20*33f37583SAndroid Build Coastguard Workerimport json
21*33f37583SAndroid Build Coastguard Workerimport logging
22*33f37583SAndroid Build Coastguard Workerimport os
23*33f37583SAndroid Build Coastguard Workerimport shutil
24*33f37583SAndroid Build Coastguard Workerimport stat
25*33f37583SAndroid Build Coastguard Workerimport subprocess
26*33f37583SAndroid Build Coastguard Workerimport tempfile
27*33f37583SAndroid Build Coastguard Workerimport unittest
28*33f37583SAndroid Build Coastguard Workerfrom importlib import resources
29*33f37583SAndroid Build Coastguard Workerfrom zipfile import ZipFile
30*33f37583SAndroid Build Coastguard Worker
31*33f37583SAndroid Build Coastguard Workerfrom apex_manifest import ValidateApexManifest
32*33f37583SAndroid Build Coastguard Workerfrom apex_manifest import ParseApexManifest
33*33f37583SAndroid Build Coastguard Worker
34*33f37583SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__)
35*33f37583SAndroid Build Coastguard Worker
36*33f37583SAndroid Build Coastguard WorkerTEST_APEX = "com.android.example.apex"
37*33f37583SAndroid Build Coastguard WorkerTEST_APEX_LEGACY = "com.android.example-legacy.apex"
38*33f37583SAndroid Build Coastguard WorkerTEST_APEX_WITH_LOGGING_PARENT = "com.android.example-logging_parent.apex"
39*33f37583SAndroid Build Coastguard WorkerTEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME = "com.android.example-overridden_package_name.apex"
40*33f37583SAndroid Build Coastguard Worker
41*33f37583SAndroid Build Coastguard WorkerTEST_PRIVATE_KEY = os.path.join("testdata", "com.android.example.apex.pem")
42*33f37583SAndroid Build Coastguard WorkerTEST_X509_KEY = os.path.join("testdata", "com.android.example.apex.x509.pem")
43*33f37583SAndroid Build Coastguard WorkerTEST_PK8_KEY = os.path.join("testdata", "com.android.example.apex.pk8")
44*33f37583SAndroid Build Coastguard WorkerTEST_AVB_PUBLIC_KEY = os.path.join("testdata", "com.android.example.apex.avbpubkey")
45*33f37583SAndroid Build Coastguard WorkerTEST_MANIFEST_JSON = os.path.join("testdata", "manifest.json")
46*33f37583SAndroid Build Coastguard Worker
47*33f37583SAndroid Build Coastguard Workerdef run(args, verbose=None, **kwargs):
48*33f37583SAndroid Build Coastguard Worker    """Creates and returns a subprocess.Popen object.
49*33f37583SAndroid Build Coastguard Worker
50*33f37583SAndroid Build Coastguard Worker    Args:
51*33f37583SAndroid Build Coastguard Worker      args: The command represented as a list of strings.
52*33f37583SAndroid Build Coastguard Worker      verbose: Whether the commands should be shown. Default to the global
53*33f37583SAndroid Build Coastguard Worker          verbosity if unspecified.
54*33f37583SAndroid Build Coastguard Worker      kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
55*33f37583SAndroid Build Coastguard Worker          stdin, etc. stdout and stderr will default to subprocess.PIPE and
56*33f37583SAndroid Build Coastguard Worker          subprocess.STDOUT respectively unless caller specifies any of them.
57*33f37583SAndroid Build Coastguard Worker          universal_newlines will default to True, as most of the users in
58*33f37583SAndroid Build Coastguard Worker          releasetools expect string output.
59*33f37583SAndroid Build Coastguard Worker
60*33f37583SAndroid Build Coastguard Worker    Returns:
61*33f37583SAndroid Build Coastguard Worker      A subprocess.Popen object.
62*33f37583SAndroid Build Coastguard Worker    """
63*33f37583SAndroid Build Coastguard Worker    if 'stdout' not in kwargs and 'stderr' not in kwargs:
64*33f37583SAndroid Build Coastguard Worker        kwargs['stdout'] = subprocess.PIPE
65*33f37583SAndroid Build Coastguard Worker        kwargs['stderr'] = subprocess.STDOUT
66*33f37583SAndroid Build Coastguard Worker    if 'universal_newlines' not in kwargs:
67*33f37583SAndroid Build Coastguard Worker        kwargs['universal_newlines'] = True
68*33f37583SAndroid Build Coastguard Worker    # Don't log any if caller explicitly says so.
69*33f37583SAndroid Build Coastguard Worker    if DEBUG_TEST:
70*33f37583SAndroid Build Coastguard Worker        print("\nRunning: \n%s\n" % " ".join(args))
71*33f37583SAndroid Build Coastguard Worker    if verbose:
72*33f37583SAndroid Build Coastguard Worker        logger.info("  Running: \"%s\"", " ".join(args))
73*33f37583SAndroid Build Coastguard Worker    return subprocess.Popen(args, **kwargs)
74*33f37583SAndroid Build Coastguard Worker
75*33f37583SAndroid Build Coastguard Worker
76*33f37583SAndroid Build Coastguard Workerdef run_host_command(args, verbose=None, **kwargs):
77*33f37583SAndroid Build Coastguard Worker    host_build_top = os.environ.get("ANDROID_BUILD_TOP")
78*33f37583SAndroid Build Coastguard Worker    if host_build_top:
79*33f37583SAndroid Build Coastguard Worker        host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin")
80*33f37583SAndroid Build Coastguard Worker        args[0] = os.path.join(host_command_dir, args[0])
81*33f37583SAndroid Build Coastguard Worker    return run_and_check_output(args, verbose, **kwargs)
82*33f37583SAndroid Build Coastguard Worker
83*33f37583SAndroid Build Coastguard Worker
84*33f37583SAndroid Build Coastguard Workerdef run_and_check_output(args, verbose=None, **kwargs):
85*33f37583SAndroid Build Coastguard Worker    """Runs the given command and returns the output.
86*33f37583SAndroid Build Coastguard Worker
87*33f37583SAndroid Build Coastguard Worker    Args:
88*33f37583SAndroid Build Coastguard Worker      args: The command represented as a list of strings.
89*33f37583SAndroid Build Coastguard Worker      verbose: Whether the commands should be shown. Default to the global
90*33f37583SAndroid Build Coastguard Worker          verbosity if unspecified.
91*33f37583SAndroid Build Coastguard Worker      kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
92*33f37583SAndroid Build Coastguard Worker          stdin, etc. stdout and stderr will default to subprocess.PIPE and
93*33f37583SAndroid Build Coastguard Worker          subprocess.STDOUT respectively unless caller specifies any of them.
94*33f37583SAndroid Build Coastguard Worker
95*33f37583SAndroid Build Coastguard Worker    Returns:
96*33f37583SAndroid Build Coastguard Worker      The output string.
97*33f37583SAndroid Build Coastguard Worker
98*33f37583SAndroid Build Coastguard Worker    Raises:
99*33f37583SAndroid Build Coastguard Worker      ExternalError: On non-zero exit from the command.
100*33f37583SAndroid Build Coastguard Worker    """
101*33f37583SAndroid Build Coastguard Worker    proc = run(args, verbose=verbose, **kwargs)
102*33f37583SAndroid Build Coastguard Worker    output, _ = proc.communicate()
103*33f37583SAndroid Build Coastguard Worker    if output is None:
104*33f37583SAndroid Build Coastguard Worker        output = ""
105*33f37583SAndroid Build Coastguard Worker    # Don't log any if caller explicitly says so.
106*33f37583SAndroid Build Coastguard Worker    if verbose:
107*33f37583SAndroid Build Coastguard Worker        logger.info("%s", output.rstrip())
108*33f37583SAndroid Build Coastguard Worker    if proc.returncode != 0:
109*33f37583SAndroid Build Coastguard Worker        raise RuntimeError(
110*33f37583SAndroid Build Coastguard Worker            "Failed to run command '{}' (exit code {}):\n{}".format(
111*33f37583SAndroid Build Coastguard Worker                args, proc.returncode, output))
112*33f37583SAndroid Build Coastguard Worker    return output
113*33f37583SAndroid Build Coastguard Worker
114*33f37583SAndroid Build Coastguard Worker
115*33f37583SAndroid Build Coastguard Workerdef get_sha1sum(file_path):
116*33f37583SAndroid Build Coastguard Worker    h = hashlib.sha256()
117*33f37583SAndroid Build Coastguard Worker
118*33f37583SAndroid Build Coastguard Worker    with open(file_path, 'rb') as file:
119*33f37583SAndroid Build Coastguard Worker        while True:
120*33f37583SAndroid Build Coastguard Worker            # Reading is buffered, so we can read smaller chunks.
121*33f37583SAndroid Build Coastguard Worker            chunk = file.read(h.block_size)
122*33f37583SAndroid Build Coastguard Worker            if not chunk:
123*33f37583SAndroid Build Coastguard Worker                break
124*33f37583SAndroid Build Coastguard Worker            h.update(chunk)
125*33f37583SAndroid Build Coastguard Worker
126*33f37583SAndroid Build Coastguard Worker    return h.hexdigest()
127*33f37583SAndroid Build Coastguard Worker
128*33f37583SAndroid Build Coastguard Worker
129*33f37583SAndroid Build Coastguard Workerdef round_up(size, unit):
130*33f37583SAndroid Build Coastguard Worker    assert unit & (unit - 1) == 0
131*33f37583SAndroid Build Coastguard Worker    return (size + unit - 1) & (~(unit - 1))
132*33f37583SAndroid Build Coastguard Worker
133*33f37583SAndroid Build Coastguard Worker# In order to debug test failures, set DEBUG_TEST to True and run the test from
134*33f37583SAndroid Build Coastguard Worker# local workstation bypassing atest, e.g.:
135*33f37583SAndroid Build Coastguard Worker# $ m apexer_test && out/host/linux-x86/nativetest64/apexer_test/apexer_test
136*33f37583SAndroid Build Coastguard Worker#
137*33f37583SAndroid Build Coastguard Worker# the test will print out the command used, and the temporary files used by the
138*33f37583SAndroid Build Coastguard Worker# test. You need to compare e.g. /tmp/test_simple_apex_input_XXXXXXXX.apex with
139*33f37583SAndroid Build Coastguard Worker# /tmp/test_simple_apex_repacked_YYYYYYYY.apex to check where they are
140*33f37583SAndroid Build Coastguard Worker# different.
141*33f37583SAndroid Build Coastguard Worker# A simple script to analyze the differences:
142*33f37583SAndroid Build Coastguard Worker#
143*33f37583SAndroid Build Coastguard Worker# FILE_INPUT=/tmp/test_simple_apex_input_XXXXXXXX.apex
144*33f37583SAndroid Build Coastguard Worker# FILE_OUTPUT=/tmp/test_simple_apex_repacked_YYYYYYYY.apex
145*33f37583SAndroid Build Coastguard Worker#
146*33f37583SAndroid Build Coastguard Worker# cd ~/tmp/
147*33f37583SAndroid Build Coastguard Worker# rm -rf input output
148*33f37583SAndroid Build Coastguard Worker# mkdir input output
149*33f37583SAndroid Build Coastguard Worker# unzip ${FILE_INPUT} -d input/
150*33f37583SAndroid Build Coastguard Worker# unzip ${FILE_OUTPUT} -d output/
151*33f37583SAndroid Build Coastguard Worker#
152*33f37583SAndroid Build Coastguard Worker# diff -r input/ output/
153*33f37583SAndroid Build Coastguard Worker#
154*33f37583SAndroid Build Coastguard Worker# For analyzing binary diffs I had mild success using the vbindiff utility.
155*33f37583SAndroid Build Coastguard WorkerDEBUG_TEST = False
156*33f37583SAndroid Build Coastguard Worker
157*33f37583SAndroid Build Coastguard Worker
158*33f37583SAndroid Build Coastguard Workerclass ApexerRebuildTest(unittest.TestCase):
159*33f37583SAndroid Build Coastguard Worker    def setUp(self):
160*33f37583SAndroid Build Coastguard Worker        self._to_cleanup = []
161*33f37583SAndroid Build Coastguard Worker        self._get_host_tools()
162*33f37583SAndroid Build Coastguard Worker
163*33f37583SAndroid Build Coastguard Worker    def tearDown(self):
164*33f37583SAndroid Build Coastguard Worker        if not DEBUG_TEST:
165*33f37583SAndroid Build Coastguard Worker            for i in self._to_cleanup:
166*33f37583SAndroid Build Coastguard Worker                if os.path.isdir(i):
167*33f37583SAndroid Build Coastguard Worker                    shutil.rmtree(i, ignore_errors=True)
168*33f37583SAndroid Build Coastguard Worker                else:
169*33f37583SAndroid Build Coastguard Worker                    os.remove(i)
170*33f37583SAndroid Build Coastguard Worker            del self._to_cleanup[:]
171*33f37583SAndroid Build Coastguard Worker        else:
172*33f37583SAndroid Build Coastguard Worker            print(self._to_cleanup)
173*33f37583SAndroid Build Coastguard Worker
174*33f37583SAndroid Build Coastguard Worker    def _get_host_tools(self):
175*33f37583SAndroid Build Coastguard Worker        dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_host_tools_")
176*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(dir_name)
177*33f37583SAndroid Build Coastguard Worker        with resources.files("apexer_test").joinpath("apexer_test_host_tools.zip").open('rb') as f:
178*33f37583SAndroid Build Coastguard Worker            with ZipFile(f, 'r') as zip_obj:
179*33f37583SAndroid Build Coastguard Worker                zip_obj.extractall(path=dir_name)
180*33f37583SAndroid Build Coastguard Worker
181*33f37583SAndroid Build Coastguard Worker        files = {}
182*33f37583SAndroid Build Coastguard Worker        for i in ["apexer", "deapexer", "avbtool", "mke2fs", "sefcontext_compile", "e2fsdroid",
183*33f37583SAndroid Build Coastguard Worker                  "resize2fs", "soong_zip", "aapt2", "merge_zips", "zipalign", "debugfs_static",
184*33f37583SAndroid Build Coastguard Worker                  "signapk.jar", "android.jar", "make_erofs", "fsck.erofs", "conv_apex_manifest"]:
185*33f37583SAndroid Build Coastguard Worker            file_path = os.path.join(dir_name, "bin", i)
186*33f37583SAndroid Build Coastguard Worker            if os.path.exists(file_path):
187*33f37583SAndroid Build Coastguard Worker                os.chmod(file_path, stat.S_IRUSR | stat.S_IXUSR)
188*33f37583SAndroid Build Coastguard Worker                files[i] = file_path
189*33f37583SAndroid Build Coastguard Worker            else:
190*33f37583SAndroid Build Coastguard Worker                files[i] = i
191*33f37583SAndroid Build Coastguard Worker        self.host_tools = files
192*33f37583SAndroid Build Coastguard Worker        self.host_tools_path = os.path.join(dir_name, "bin")
193*33f37583SAndroid Build Coastguard Worker
194*33f37583SAndroid Build Coastguard Worker        path = self.host_tools_path
195*33f37583SAndroid Build Coastguard Worker        if "PATH" in os.environ:
196*33f37583SAndroid Build Coastguard Worker            path += ":" + os.environ["PATH"]
197*33f37583SAndroid Build Coastguard Worker        os.environ["PATH"] = path
198*33f37583SAndroid Build Coastguard Worker
199*33f37583SAndroid Build Coastguard Worker        ld_library_path = os.path.join(dir_name, "lib64")
200*33f37583SAndroid Build Coastguard Worker        if "LD_LIBRARY_PATH" in os.environ:
201*33f37583SAndroid Build Coastguard Worker            ld_library_path += ":" + os.environ["LD_LIBRARY_PATH"]
202*33f37583SAndroid Build Coastguard Worker        if "ANDROID_HOST_OUT" in os.environ:
203*33f37583SAndroid Build Coastguard Worker            ld_library_path += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64")
204*33f37583SAndroid Build Coastguard Worker        os.environ["LD_LIBRARY_PATH"] = ld_library_path
205*33f37583SAndroid Build Coastguard Worker
206*33f37583SAndroid Build Coastguard Worker    def _extract_resource(self, resource_name):
207*33f37583SAndroid Build Coastguard Worker        with (
208*33f37583SAndroid Build Coastguard Worker            resources.files("apexer_test").joinpath(resource_name).open('rb') as f,
209*33f37583SAndroid Build Coastguard Worker            tempfile.NamedTemporaryFile(prefix=resource_name.replace('/', '_'), delete=False) as f2,
210*33f37583SAndroid Build Coastguard Worker        ):
211*33f37583SAndroid Build Coastguard Worker            self._to_cleanup.append(f2.name)
212*33f37583SAndroid Build Coastguard Worker            shutil.copyfileobj(f, f2)
213*33f37583SAndroid Build Coastguard Worker            return f2.name
214*33f37583SAndroid Build Coastguard Worker
215*33f37583SAndroid Build Coastguard Worker    def _get_container_files(self, apex_file_path):
216*33f37583SAndroid Build Coastguard Worker        dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_container_files_")
217*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(dir_name)
218*33f37583SAndroid Build Coastguard Worker        with ZipFile(apex_file_path, 'r') as zip_obj:
219*33f37583SAndroid Build Coastguard Worker            zip_obj.extractall(path=dir_name)
220*33f37583SAndroid Build Coastguard Worker        files = {}
221*33f37583SAndroid Build Coastguard Worker        for i in ["apex_manifest.json", "apex_manifest.pb",
222*33f37583SAndroid Build Coastguard Worker                  "apex_build_info.pb", "assets",
223*33f37583SAndroid Build Coastguard Worker                  "apex_payload.img", "apex_payload.zip"]:
224*33f37583SAndroid Build Coastguard Worker            file_path = os.path.join(dir_name, i)
225*33f37583SAndroid Build Coastguard Worker            if os.path.exists(file_path):
226*33f37583SAndroid Build Coastguard Worker                files[i] = file_path
227*33f37583SAndroid Build Coastguard Worker        self.assertIn("apex_manifest.pb", files)
228*33f37583SAndroid Build Coastguard Worker        self.assertIn("apex_build_info.pb", files)
229*33f37583SAndroid Build Coastguard Worker
230*33f37583SAndroid Build Coastguard Worker        image_file = None
231*33f37583SAndroid Build Coastguard Worker        if "apex_payload.img" in files:
232*33f37583SAndroid Build Coastguard Worker            image_file = files["apex_payload.img"]
233*33f37583SAndroid Build Coastguard Worker        elif "apex_payload.zip" in files:
234*33f37583SAndroid Build Coastguard Worker            image_file = files["apex_payload.zip"]
235*33f37583SAndroid Build Coastguard Worker        self.assertIsNotNone(image_file)
236*33f37583SAndroid Build Coastguard Worker        files["apex_payload"] = image_file
237*33f37583SAndroid Build Coastguard Worker
238*33f37583SAndroid Build Coastguard Worker        return files
239*33f37583SAndroid Build Coastguard Worker
240*33f37583SAndroid Build Coastguard Worker    def _extract_payload_from_img(self, img_file_path):
241*33f37583SAndroid Build Coastguard Worker        dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_extracted_payload_")
242*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(dir_name)
243*33f37583SAndroid Build Coastguard Worker        cmd = ["debugfs_static", '-R', 'rdump ./ %s' % dir_name, img_file_path]
244*33f37583SAndroid Build Coastguard Worker        run_host_command(cmd)
245*33f37583SAndroid Build Coastguard Worker
246*33f37583SAndroid Build Coastguard Worker        # Remove payload files added by apexer and e2fs tools.
247*33f37583SAndroid Build Coastguard Worker        for i in ["apex_manifest.json", "apex_manifest.pb"]:
248*33f37583SAndroid Build Coastguard Worker            if os.path.exists(os.path.join(dir_name, i)):
249*33f37583SAndroid Build Coastguard Worker                os.remove(os.path.join(dir_name, i))
250*33f37583SAndroid Build Coastguard Worker        if os.path.isdir(os.path.join(dir_name, "lost+found")):
251*33f37583SAndroid Build Coastguard Worker            shutil.rmtree(os.path.join(dir_name, "lost+found"))
252*33f37583SAndroid Build Coastguard Worker        return dir_name
253*33f37583SAndroid Build Coastguard Worker
254*33f37583SAndroid Build Coastguard Worker    def _extract_payload(self, apex_file_path):
255*33f37583SAndroid Build Coastguard Worker        dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_extracted_payload_")
256*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(dir_name)
257*33f37583SAndroid Build Coastguard Worker        cmd = ["deapexer", "--debugfs_path", self.host_tools["debugfs_static"],
258*33f37583SAndroid Build Coastguard Worker               "--fsckerofs_path", self.host_tools["fsck.erofs"], "extract",
259*33f37583SAndroid Build Coastguard Worker               apex_file_path, dir_name]
260*33f37583SAndroid Build Coastguard Worker        run_host_command(cmd)
261*33f37583SAndroid Build Coastguard Worker
262*33f37583SAndroid Build Coastguard Worker        # Remove payload files added by apexer and e2fs tools.
263*33f37583SAndroid Build Coastguard Worker        for i in ["apex_manifest.json", "apex_manifest.pb"]:
264*33f37583SAndroid Build Coastguard Worker            if os.path.exists(os.path.join(dir_name, i)):
265*33f37583SAndroid Build Coastguard Worker                os.remove(os.path.join(dir_name, i))
266*33f37583SAndroid Build Coastguard Worker        if os.path.isdir(os.path.join(dir_name, "lost+found")):
267*33f37583SAndroid Build Coastguard Worker            shutil.rmtree(os.path.join(dir_name, "lost+found"))
268*33f37583SAndroid Build Coastguard Worker        return dir_name
269*33f37583SAndroid Build Coastguard Worker
270*33f37583SAndroid Build Coastguard Worker    def _run_apexer(self, container_files, payload_dir, args=[]):
271*33f37583SAndroid Build Coastguard Worker        unsigned_payload_only = False
272*33f37583SAndroid Build Coastguard Worker        payload_only = False
273*33f37583SAndroid Build Coastguard Worker        if "--unsigned_payload_only" in args:
274*33f37583SAndroid Build Coastguard Worker            unsigned_payload_only = True
275*33f37583SAndroid Build Coastguard Worker        if unsigned_payload_only or "--payload_only" in args:
276*33f37583SAndroid Build Coastguard Worker            payload_only = True
277*33f37583SAndroid Build Coastguard Worker
278*33f37583SAndroid Build Coastguard Worker        os.environ["APEXER_TOOL_PATH"] = (self.host_tools_path +
279*33f37583SAndroid Build Coastguard Worker            ":out/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin")
280*33f37583SAndroid Build Coastguard Worker        cmd = ["apexer", "--force", "--include_build_info", "--do_not_check_keyname"]
281*33f37583SAndroid Build Coastguard Worker        if DEBUG_TEST:
282*33f37583SAndroid Build Coastguard Worker            cmd.append('-v')
283*33f37583SAndroid Build Coastguard Worker        cmd.extend(["--apexer_tool_path", os.environ["APEXER_TOOL_PATH"]])
284*33f37583SAndroid Build Coastguard Worker        cmd.extend(["--android_jar_path", self.host_tools["android.jar"]])
285*33f37583SAndroid Build Coastguard Worker        cmd.extend(["--manifest", container_files["apex_manifest.pb"]])
286*33f37583SAndroid Build Coastguard Worker        if "apex_manifest.json" in container_files:
287*33f37583SAndroid Build Coastguard Worker            cmd.extend(["--manifest_json", container_files["apex_manifest.json"]])
288*33f37583SAndroid Build Coastguard Worker        cmd.extend(["--build_info", container_files["apex_build_info.pb"]])
289*33f37583SAndroid Build Coastguard Worker        if not payload_only and "assets" in container_files:
290*33f37583SAndroid Build Coastguard Worker            cmd.extend(["--assets_dir", container_files["assets"]])
291*33f37583SAndroid Build Coastguard Worker        if not unsigned_payload_only:
292*33f37583SAndroid Build Coastguard Worker            cmd.extend(["--key", self._extract_resource(TEST_PRIVATE_KEY)])
293*33f37583SAndroid Build Coastguard Worker            cmd.extend(["--pubkey", self._extract_resource(TEST_AVB_PUBLIC_KEY)])
294*33f37583SAndroid Build Coastguard Worker        cmd.extend(args)
295*33f37583SAndroid Build Coastguard Worker
296*33f37583SAndroid Build Coastguard Worker        # Decide on output file name
297*33f37583SAndroid Build Coastguard Worker        apex_suffix = ".apex.unsigned"
298*33f37583SAndroid Build Coastguard Worker        if payload_only:
299*33f37583SAndroid Build Coastguard Worker            apex_suffix = ".payload"
300*33f37583SAndroid Build Coastguard Worker        fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=apex_suffix)
301*33f37583SAndroid Build Coastguard Worker        os.close(fd)
302*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(fn)
303*33f37583SAndroid Build Coastguard Worker        cmd.extend([payload_dir, fn])
304*33f37583SAndroid Build Coastguard Worker
305*33f37583SAndroid Build Coastguard Worker        run_host_command(cmd)
306*33f37583SAndroid Build Coastguard Worker        return fn
307*33f37583SAndroid Build Coastguard Worker
308*33f37583SAndroid Build Coastguard Worker    def _get_java_toolchain(self):
309*33f37583SAndroid Build Coastguard Worker        java_toolchain = "java"
310*33f37583SAndroid Build Coastguard Worker        if os.path.isfile("prebuilts/jdk/jdk21/linux-x86/bin/java"):
311*33f37583SAndroid Build Coastguard Worker            java_toolchain = "prebuilts/jdk/jdk21/linux-x86/bin/java"
312*33f37583SAndroid Build Coastguard Worker        elif os.path.isfile("/jdk/jdk21/linux-x86/bin/java"):
313*33f37583SAndroid Build Coastguard Worker            java_toolchain = "/jdk/jdk21/linux-x86/bin/java"
314*33f37583SAndroid Build Coastguard Worker        elif "ANDROID_JAVA_TOOLCHAIN" in os.environ:
315*33f37583SAndroid Build Coastguard Worker            java_toolchain = os.path.join(os.environ["ANDROID_JAVA_TOOLCHAIN"], "java")
316*33f37583SAndroid Build Coastguard Worker        elif "ANDROID_JAVA_HOME" in os.environ:
317*33f37583SAndroid Build Coastguard Worker            java_toolchain = os.path.join(os.environ["ANDROID_JAVA_HOME"], "bin", "java")
318*33f37583SAndroid Build Coastguard Worker        elif "JAVA_HOME" in os.environ:
319*33f37583SAndroid Build Coastguard Worker            java_toolchain = os.path.join(os.environ["JAVA_HOME"], "bin", "java")
320*33f37583SAndroid Build Coastguard Worker
321*33f37583SAndroid Build Coastguard Worker        java_dep_lib = os.environ["LD_LIBRARY_PATH"]
322*33f37583SAndroid Build Coastguard Worker        if "ANDROID_HOST_OUT" in os.environ:
323*33f37583SAndroid Build Coastguard Worker            java_dep_lib += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64")
324*33f37583SAndroid Build Coastguard Worker        if "ANDROID_BUILD_TOP" in os.environ:
325*33f37583SAndroid Build Coastguard Worker            java_dep_lib += ":" + os.path.join(os.environ["ANDROID_BUILD_TOP"],
326*33f37583SAndroid Build Coastguard Worker                "out/host/linux-x86/lib64")
327*33f37583SAndroid Build Coastguard Worker
328*33f37583SAndroid Build Coastguard Worker        return [java_toolchain, java_dep_lib]
329*33f37583SAndroid Build Coastguard Worker
330*33f37583SAndroid Build Coastguard Worker    def _sign_apk_container(self, unsigned_apex):
331*33f37583SAndroid Build Coastguard Worker        fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=".apex")
332*33f37583SAndroid Build Coastguard Worker        os.close(fd)
333*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(fn)
334*33f37583SAndroid Build Coastguard Worker        java_toolchain, java_dep_lib = self._get_java_toolchain()
335*33f37583SAndroid Build Coastguard Worker        cmd = [
336*33f37583SAndroid Build Coastguard Worker            java_toolchain,
337*33f37583SAndroid Build Coastguard Worker            "-Djava.library.path=" + java_dep_lib,
338*33f37583SAndroid Build Coastguard Worker            "-jar", self.host_tools['signapk.jar'],
339*33f37583SAndroid Build Coastguard Worker            "-a", "4096", "--align-file-size",
340*33f37583SAndroid Build Coastguard Worker            self._extract_resource(TEST_X509_KEY),
341*33f37583SAndroid Build Coastguard Worker            self._extract_resource(TEST_PK8_KEY),
342*33f37583SAndroid Build Coastguard Worker            unsigned_apex, fn]
343*33f37583SAndroid Build Coastguard Worker        run_and_check_output(cmd)
344*33f37583SAndroid Build Coastguard Worker        return fn
345*33f37583SAndroid Build Coastguard Worker
346*33f37583SAndroid Build Coastguard Worker    def _sign_payload(self, container_files, unsigned_payload):
347*33f37583SAndroid Build Coastguard Worker        fd, signed_payload = \
348*33f37583SAndroid Build Coastguard Worker            tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=".payload")
349*33f37583SAndroid Build Coastguard Worker        os.close(fd)
350*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(signed_payload)
351*33f37583SAndroid Build Coastguard Worker        shutil.copyfile(unsigned_payload, signed_payload)
352*33f37583SAndroid Build Coastguard Worker
353*33f37583SAndroid Build Coastguard Worker        cmd = ['avbtool']
354*33f37583SAndroid Build Coastguard Worker        cmd.append('add_hashtree_footer')
355*33f37583SAndroid Build Coastguard Worker        cmd.append('--do_not_generate_fec')
356*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--algorithm', 'SHA256_RSA4096'])
357*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--hash_algorithm', 'sha256'])
358*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--key', self._extract_resource(TEST_PRIVATE_KEY)])
359*33f37583SAndroid Build Coastguard Worker        manifest_apex = ParseApexManifest(container_files["apex_manifest.pb"])
360*33f37583SAndroid Build Coastguard Worker        ValidateApexManifest(manifest_apex)
361*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--prop', 'apex.key:' + manifest_apex.name])
362*33f37583SAndroid Build Coastguard Worker        # Set up the salt based on manifest content which includes name
363*33f37583SAndroid Build Coastguard Worker        # and version
364*33f37583SAndroid Build Coastguard Worker        salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest()
365*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--salt', salt])
366*33f37583SAndroid Build Coastguard Worker        cmd.extend(['--image', signed_payload])
367*33f37583SAndroid Build Coastguard Worker        run_and_check_output(cmd)
368*33f37583SAndroid Build Coastguard Worker
369*33f37583SAndroid Build Coastguard Worker        return signed_payload
370*33f37583SAndroid Build Coastguard Worker
371*33f37583SAndroid Build Coastguard Worker    def _verify_payload(self, payload):
372*33f37583SAndroid Build Coastguard Worker        """Verifies that the payload is properly signed by avbtool"""
373*33f37583SAndroid Build Coastguard Worker        cmd = ["avbtool", "verify_image", "--image", payload, "--accept_zeroed_hashtree"]
374*33f37583SAndroid Build Coastguard Worker        run_and_check_output(cmd)
375*33f37583SAndroid Build Coastguard Worker
376*33f37583SAndroid Build Coastguard Worker    def _run_build_test(self, apex_name):
377*33f37583SAndroid Build Coastguard Worker        apex_file_path = self._extract_resource(apex_name + ".apex")
378*33f37583SAndroid Build Coastguard Worker        if DEBUG_TEST:
379*33f37583SAndroid Build Coastguard Worker            fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_input_", suffix=".apex")
380*33f37583SAndroid Build Coastguard Worker            os.close(fd)
381*33f37583SAndroid Build Coastguard Worker            shutil.copyfile(apex_file_path, fn)
382*33f37583SAndroid Build Coastguard Worker            self._to_cleanup.append(fn)
383*33f37583SAndroid Build Coastguard Worker        container_files = self._get_container_files(apex_file_path)
384*33f37583SAndroid Build Coastguard Worker        payload_dir = self._extract_payload(apex_file_path)
385*33f37583SAndroid Build Coastguard Worker        repack_apex_file_path = self._run_apexer(container_files, payload_dir)
386*33f37583SAndroid Build Coastguard Worker        resigned_apex_file_path = self._sign_apk_container(repack_apex_file_path)
387*33f37583SAndroid Build Coastguard Worker        self.assertEqual(get_sha1sum(apex_file_path), get_sha1sum(resigned_apex_file_path))
388*33f37583SAndroid Build Coastguard Worker
389*33f37583SAndroid Build Coastguard Worker    def test_simple_apex(self):
390*33f37583SAndroid Build Coastguard Worker        self._run_build_test(TEST_APEX)
391*33f37583SAndroid Build Coastguard Worker
392*33f37583SAndroid Build Coastguard Worker    def test_legacy_apex(self):
393*33f37583SAndroid Build Coastguard Worker        self._run_build_test(TEST_APEX_LEGACY)
394*33f37583SAndroid Build Coastguard Worker
395*33f37583SAndroid Build Coastguard Worker    def test_output_payload_only(self):
396*33f37583SAndroid Build Coastguard Worker        """Assert that payload-only output from apexer is same as the payload we get by unzipping
397*33f37583SAndroid Build Coastguard Worker        apex.
398*33f37583SAndroid Build Coastguard Worker        """
399*33f37583SAndroid Build Coastguard Worker        apex_file_path = self._extract_resource(TEST_APEX + ".apex")
400*33f37583SAndroid Build Coastguard Worker        container_files = self._get_container_files(apex_file_path)
401*33f37583SAndroid Build Coastguard Worker        payload_dir = self._extract_payload(apex_file_path)
402*33f37583SAndroid Build Coastguard Worker        payload_only_file_path = self._run_apexer(container_files, payload_dir, ["--payload_only"])
403*33f37583SAndroid Build Coastguard Worker        self._verify_payload(payload_only_file_path)
404*33f37583SAndroid Build Coastguard Worker        self.assertEqual(get_sha1sum(payload_only_file_path),
405*33f37583SAndroid Build Coastguard Worker                         get_sha1sum(container_files["apex_payload"]))
406*33f37583SAndroid Build Coastguard Worker
407*33f37583SAndroid Build Coastguard Worker    def test_output_unsigned_payload_only(self):
408*33f37583SAndroid Build Coastguard Worker        """Assert that when unsigned-payload-only output from apexer is signed by the avb key, it is
409*33f37583SAndroid Build Coastguard Worker        same as the payload we get by unzipping apex.
410*33f37583SAndroid Build Coastguard Worker        """
411*33f37583SAndroid Build Coastguard Worker        apex_file_path = self._extract_resource(TEST_APEX + ".apex")
412*33f37583SAndroid Build Coastguard Worker        container_files = self._get_container_files(apex_file_path)
413*33f37583SAndroid Build Coastguard Worker        payload_dir = self._extract_payload(apex_file_path)
414*33f37583SAndroid Build Coastguard Worker        unsigned_payload_only_file_path = self._run_apexer(container_files, payload_dir,
415*33f37583SAndroid Build Coastguard Worker                                                  ["--unsigned_payload_only"])
416*33f37583SAndroid Build Coastguard Worker        with self.assertRaises(RuntimeError) as error:
417*33f37583SAndroid Build Coastguard Worker            self._verify_payload(unsigned_payload_only_file_path)
418*33f37583SAndroid Build Coastguard Worker        self.assertIn("Given image does not look like a vbmeta image", str(error.exception))
419*33f37583SAndroid Build Coastguard Worker        signed_payload = self._sign_payload(container_files, unsigned_payload_only_file_path)
420*33f37583SAndroid Build Coastguard Worker        self.assertEqual(get_sha1sum(signed_payload),
421*33f37583SAndroid Build Coastguard Worker                         get_sha1sum(container_files["apex_payload"]))
422*33f37583SAndroid Build Coastguard Worker
423*33f37583SAndroid Build Coastguard Worker    def test_apex_with_logging_parent(self):
424*33f37583SAndroid Build Coastguard Worker      self._run_build_test(TEST_APEX_WITH_LOGGING_PARENT)
425*33f37583SAndroid Build Coastguard Worker
426*33f37583SAndroid Build Coastguard Worker    def test_apex_with_overridden_package_name(self):
427*33f37583SAndroid Build Coastguard Worker      self._run_build_test(TEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME)
428*33f37583SAndroid Build Coastguard Worker
429*33f37583SAndroid Build Coastguard Worker    def test_conv_apex_manifest(self):
430*33f37583SAndroid Build Coastguard Worker        # .pb generation from json
431*33f37583SAndroid Build Coastguard Worker        manifest_json_path = self._extract_resource(TEST_MANIFEST_JSON)
432*33f37583SAndroid Build Coastguard Worker
433*33f37583SAndroid Build Coastguard Worker        fd, fn = tempfile.mkstemp(prefix=self._testMethodName + "_manifest_", suffix=".pb")
434*33f37583SAndroid Build Coastguard Worker        os.close(fd)
435*33f37583SAndroid Build Coastguard Worker        self._to_cleanup.append(fn)
436*33f37583SAndroid Build Coastguard Worker        cmd = [
437*33f37583SAndroid Build Coastguard Worker            "conv_apex_manifest",
438*33f37583SAndroid Build Coastguard Worker            "proto",
439*33f37583SAndroid Build Coastguard Worker            manifest_json_path,
440*33f37583SAndroid Build Coastguard Worker            "-o", fn]
441*33f37583SAndroid Build Coastguard Worker        run_and_check_output(cmd)
442*33f37583SAndroid Build Coastguard Worker
443*33f37583SAndroid Build Coastguard Worker        with open(manifest_json_path) as fd_json:
444*33f37583SAndroid Build Coastguard Worker            manifest_json = json.load(fd_json)
445*33f37583SAndroid Build Coastguard Worker        manifest_apex = ParseApexManifest(fn)
446*33f37583SAndroid Build Coastguard Worker        ValidateApexManifest(manifest_apex)
447*33f37583SAndroid Build Coastguard Worker
448*33f37583SAndroid Build Coastguard Worker        self.assertEqual(manifest_apex.name, manifest_json["name"])
449*33f37583SAndroid Build Coastguard Worker        self.assertEqual(manifest_apex.version, manifest_json["version"])
450*33f37583SAndroid Build Coastguard Worker
451*33f37583SAndroid Build Coastguard Worker        # setprop check on already generated .pb
452*33f37583SAndroid Build Coastguard Worker        next_version = 20
453*33f37583SAndroid Build Coastguard Worker        cmd = [
454*33f37583SAndroid Build Coastguard Worker            "conv_apex_manifest",
455*33f37583SAndroid Build Coastguard Worker            "setprop",
456*33f37583SAndroid Build Coastguard Worker            "version", str(next_version),
457*33f37583SAndroid Build Coastguard Worker            fn]
458*33f37583SAndroid Build Coastguard Worker        run_and_check_output(cmd)
459*33f37583SAndroid Build Coastguard Worker
460*33f37583SAndroid Build Coastguard Worker        manifest_apex = ParseApexManifest(fn)
461*33f37583SAndroid Build Coastguard Worker        ValidateApexManifest(manifest_apex)
462*33f37583SAndroid Build Coastguard Worker
463*33f37583SAndroid Build Coastguard Worker        self.assertEqual(manifest_apex.version, next_version)
464*33f37583SAndroid Build Coastguard Worker
465*33f37583SAndroid Build Coastguard Worker
466*33f37583SAndroid Build Coastguard Worker
467*33f37583SAndroid Build Coastguard Workerif __name__ == '__main__':
468*33f37583SAndroid Build Coastguard Worker    unittest.main(verbosity=2)
469