1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.pm.dex;
18
19import static com.google.common.truth.Truth.assertThat;
20
21import android.content.Context;
22import android.support.test.InstrumentationRegistry;
23import android.support.test.filters.LargeTest;
24import android.util.EventLog;
25import dalvik.system.DexClassLoader;
26
27import org.junit.After;
28import org.junit.AfterClass;
29import org.junit.Before;
30import org.junit.BeforeClass;
31import org.junit.Test;
32import org.junit.runner.RunWith;
33import org.junit.runners.JUnit4;
34
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.security.MessageDigest;
40import java.util.ArrayList;
41import java.util.Formatter;
42import java.util.List;
43
44/**
45 * Integration tests for {@link com.android.server.pm.dex.DexLogger}.
46 *
47 * The setup for the test dynamically loads code in a jar extracted
48 * from our assets (a secondary dex file).
49 *
50 * We then use adb to trigger secondary dex file reconcilation (and
51 * wait for it to complete). As a side-effect of this DexLogger should
52 * be notified of the file and should log the hash of the file's name
53 * and content.  We verify that this message appears in the event log.
54 *
55 * Run with "atest DexLoggerIntegrationTests".
56 */
57@LargeTest
58@RunWith(JUnit4.class)
59public final class DexLoggerIntegrationTests {
60
61    private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
62
63    // Event log tag used for SNET related events
64    private static final int SNET_TAG = 0x534e4554;
65    // Subtag used to distinguish dynamic code loading events
66    private static final String DCL_SUBTAG = "dcl";
67
68    // Obtained via "echo -n copied.jar | sha256sum"
69    private static final String EXPECTED_NAME_HASH =
70            "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
71
72    private static String expectedContentHash;
73
74    @BeforeClass
75    public static void setUpAll() throws Exception {
76        Context context = InstrumentationRegistry.getTargetContext();
77        MessageDigest hasher = MessageDigest.getInstance("SHA-256");
78
79        // Copy the jar from our Java resources to a private data directory
80        File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
81        Class<?> thisClass = DexLoggerIntegrationTests.class;
82        try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
83                OutputStream output = new FileOutputStream(privateCopy)) {
84            byte[] buffer = new byte[1024];
85            while (true) {
86                int numRead = input.read(buffer);
87                if (numRead < 0) {
88                    break;
89                }
90                output.write(buffer, 0, numRead);
91                hasher.update(buffer, 0, numRead);
92            }
93        }
94
95        // Remember the SHA-256 of the file content to check that it is the same as
96        // the value we see logged.
97        Formatter formatter = new Formatter();
98        for (byte b : hasher.digest()) {
99            formatter.format("%02X", b);
100        }
101        expectedContentHash = formatter.toString();
102
103        // Feed the jar to a class loader and make sure it contains what we expect.
104        ClassLoader loader =
105                new DexClassLoader(
106                    privateCopy.toString(), null, null, context.getClass().getClassLoader());
107        loader.loadClass("com.android.dcl.Simple");
108    }
109
110    @Test
111    public void testDexLoggerReconcileGeneratesEvents() throws Exception {
112        int[] tagList = new int[] { SNET_TAG };
113        List<EventLog.Event> events = new ArrayList<>();
114
115        // There may already be events in the event log - figure out the most recent one
116        EventLog.readEvents(tagList, events);
117        long previousEventNanos =
118                events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
119        events.clear();
120
121        Process process = Runtime.getRuntime().exec(
122            "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
123        int exitCode = process.waitFor();
124        assertThat(exitCode).isEqualTo(0);
125
126        int myUid = android.os.Process.myUid();
127        String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
128
129        EventLog.readEvents(tagList, events);
130        boolean found = false;
131        for (EventLog.Event event : events) {
132            if (event.getTimeNanos() <= previousEventNanos) {
133                continue;
134            }
135            Object[] data = (Object[]) event.getData();
136
137            // We only care about DCL events that we generated.
138            String subTag = (String) data[0];
139            if (!DCL_SUBTAG.equals(subTag)) {
140                continue;
141            }
142            int uid = (int) data[1];
143            if (uid != myUid) {
144                continue;
145            }
146
147            String message = (String) data[2];
148            assertThat(message).isEqualTo(expectedMessage);
149            found = true;
150        }
151
152        assertThat(found).isTrue();
153    }
154}
155