blob: 8ef15d869a0b3e4741780b057a353457ec0187ad [file] [log] [blame]
Alan Stokes761d6182017-12-19 11:48:19 +00001/*
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
Alan Stokes55d11f62018-01-09 10:04:21 +000017package com.android.server.pm.dex;
Alan Stokes761d6182017-12-19 11:48:19 +000018
19import static com.google.common.truth.Truth.assertThat;
Alan Stokese9d33142018-12-19 09:31:21 +000020import static com.google.common.truth.Truth.assertWithMessage;
Alan Stokes761d6182017-12-19 11:48:19 +000021
Alan Stokesbe9ec972018-12-10 14:07:47 +000022import android.app.UiAutomation;
Alan Stokes761d6182017-12-19 11:48:19 +000023import android.content.Context;
Alan Stokesa3039532019-01-25 14:07:23 +000024import android.os.Build;
Alan Stokesbe9ec972018-12-10 14:07:47 +000025import android.os.ParcelFileDescriptor;
26import android.os.SystemClock;
Alan Stokes761d6182017-12-19 11:48:19 +000027import android.support.test.InstrumentationRegistry;
Alan Stokes55d11f62018-01-09 10:04:21 +000028import android.support.test.filters.LargeTest;
Alan Stokes761d6182017-12-19 11:48:19 +000029import android.util.EventLog;
Alan Stokese9d33142018-12-19 09:31:21 +000030import android.util.EventLog.Event;
Alan Stokesbe9ec972018-12-10 14:07:47 +000031
Alan Stokes761d6182017-12-19 11:48:19 +000032import dalvik.system.DexClassLoader;
33
Alan Stokes761d6182017-12-19 11:48:19 +000034import org.junit.Before;
35import org.junit.BeforeClass;
36import org.junit.Test;
37import org.junit.runner.RunWith;
38import org.junit.runners.JUnit4;
39
Alan Stokesbe9ec972018-12-10 14:07:47 +000040import java.io.ByteArrayOutputStream;
Alan Stokes761d6182017-12-19 11:48:19 +000041import java.io.File;
42import java.io.FileOutputStream;
43import java.io.InputStream;
44import java.io.OutputStream;
45import java.security.MessageDigest;
46import java.util.ArrayList;
47import java.util.Formatter;
48import java.util.List;
Alan Stokesbe9ec972018-12-10 14:07:47 +000049import java.util.concurrent.TimeUnit;
Alan Stokes761d6182017-12-19 11:48:19 +000050
51/**
Alan Stokes8d3b7a42019-02-19 17:14:42 +000052 * Integration tests for {@link DynamicCodeLogger}.
Alan Stokes761d6182017-12-19 11:48:19 +000053 *
54 * The setup for the test dynamically loads code in a jar extracted
55 * from our assets (a secondary dex file).
56 *
Alan Stokesbe9ec972018-12-10 14:07:47 +000057 * We then use shell commands to trigger dynamic code logging (and wait
Alan Stokes96efc0282019-02-05 15:10:36 +000058 * for it to complete). This causes DynamicCodeLogger to log the hash of the
Alan Stokesbe9ec972018-12-10 14:07:47 +000059 * file's name and content. We verify that this message appears in
60 * the event log.
Alan Stokes761d6182017-12-19 11:48:19 +000061 *
Alan Stokes8d3b7a42019-02-19 17:14:42 +000062 * Run with "atest DynamicCodeLoggerIntegrationTests".
Alan Stokes761d6182017-12-19 11:48:19 +000063 */
Alan Stokes55d11f62018-01-09 10:04:21 +000064@LargeTest
Alan Stokes761d6182017-12-19 11:48:19 +000065@RunWith(JUnit4.class)
Alan Stokes8d3b7a42019-02-19 17:14:42 +000066public final class DynamicCodeLoggerIntegrationTests {
Alan Stokes761d6182017-12-19 11:48:19 +000067
Alan Stokes206a69d2019-02-04 15:32:12 +000068 private static final String SHA_256 = "SHA-256";
69
Alan Stokes1e24bf62018-01-05 14:36:00 +000070 // Event log tag used for SNET related events
Alan Stokes761d6182017-12-19 11:48:19 +000071 private static final int SNET_TAG = 0x534e4554;
Alan Stokesbe9ec972018-12-10 14:07:47 +000072
Alan Stokese9d33142018-12-19 09:31:21 +000073 // Subtags used to distinguish dynamic code loading events
74 private static final String DCL_DEX_SUBTAG = "dcl";
75 private static final String DCL_NATIVE_SUBTAG = "dcln";
Alan Stokes761d6182017-12-19 11:48:19 +000076
Alan Stokese9d33142018-12-19 09:31:21 +000077 // These are job IDs from DynamicCodeLoggingService
78 private static final int IDLE_LOGGING_JOB_ID = 2030028;
79 private static final int AUDIT_WATCHING_JOB_ID = 203142925;
Alan Stokesbe9ec972018-12-10 14:07:47 +000080
Alan Stokes206a69d2019-02-04 15:32:12 +000081 // For tests that rely on parsing audit logs, how often to retry. (There are many reasons why
82 // we might not see the audit logs, including throttling and delays in log generation, so to
83 // avoid flakiness we run these tests multiple times, allowing progressively longer between
84 // code loading and checking the logs on each try.)
85 private static final int AUDIT_LOG_RETRIES = 10;
86 private static final int RETRY_DELAY_MS = 2_000;
87
Alan Stokesbe9ec972018-12-10 14:07:47 +000088 private static Context sContext;
89 private static int sMyUid;
Alan Stokes761d6182017-12-19 11:48:19 +000090
91 @BeforeClass
Alan Stokesbe9ec972018-12-10 14:07:47 +000092 public static void setUpAll() {
93 sContext = InstrumentationRegistry.getTargetContext();
94 sMyUid = android.os.Process.myUid();
95 }
96
97 @Before
98 public void primeEventLog() {
99 // Force a round trip to logd to make sure everything is up to date.
100 // Without this the first test passes and others don't - we don't see new events in the
101 // log. The exact reason is unclear.
102 EventLog.writeEvent(SNET_TAG, "Dummy event");
Alan Stokese9d33142018-12-19 09:31:21 +0000103
104 // Audit log messages are throttled by the kernel (at the request of logd) to 5 per
105 // second, so running the tests too quickly in sequence means we lose some and get
106 // spurious failures. Sigh.
107 SystemClock.sleep(1000);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000108 }
109
110 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000111 public void testGeneratesEvents_standardClassLoader() throws Exception {
Alan Stokese9d33142018-12-19 09:31:21 +0000112 File privateCopyFile = privateFile("copied.jar");
Alan Stokesbe9ec972018-12-10 14:07:47 +0000113 // Obtained via "echo -n copied.jar | sha256sum"
114 String expectedNameHash =
115 "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
Alan Stokese9d33142018-12-19 09:31:21 +0000116 String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000117
118 // Feed the jar to a class loader and make sure it contains what we expect.
119 ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
120 ClassLoader loader =
121 new DexClassLoader(privateCopyFile.toString(), null, null, parentClassLoader);
122 loader.loadClass("com.android.dcl.Simple");
123
124 // And make sure we log events about it
125 long previousEventNanos = mostRecentEventTimeNanos();
Alan Stokese9d33142018-12-19 09:31:21 +0000126 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000127
Alan Stokese9d33142018-12-19 09:31:21 +0000128 assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
129 expectedNameHash, expectedContentHash);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000130 }
131
132 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000133 public void testGeneratesEvents_unknownClassLoader() throws Exception {
Alan Stokese9d33142018-12-19 09:31:21 +0000134 File privateCopyFile = privateFile("copied2.jar");
Alan Stokesbe9ec972018-12-10 14:07:47 +0000135 String expectedNameHash =
136 "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
Alan Stokese9d33142018-12-19 09:31:21 +0000137 String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000138
139 // This time make sure an unknown class loader is an ancestor of the class loader we use.
140 ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
141 ClassLoader unknownClassLoader = new UnknownClassLoader(knownClassLoader);
142 ClassLoader loader =
143 new DexClassLoader(privateCopyFile.toString(), null, null, unknownClassLoader);
144 loader.loadClass("com.android.dcl.Simple");
145
146 // And make sure we log events about it
147 long previousEventNanos = mostRecentEventTimeNanos();
Alan Stokese9d33142018-12-19 09:31:21 +0000148 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000149
Alan Stokese9d33142018-12-19 09:31:21 +0000150 assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
151 expectedNameHash, expectedContentHash);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000152 }
153
Alan Stokese9d33142018-12-19 09:31:21 +0000154 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000155 public void testGeneratesEvents_nativeLibrary() throws Exception {
Alan Stokes206a69d2019-02-04 15:32:12 +0000156 new TestNativeCodeWithRetries() {
157 @Override
158 protected void loadNativeCode(int tryNumber) throws Exception {
159 // We need to use a different file name for each retry, because once a file is
160 // loaded, re-loading it has no effect.
161 String privateCopyName = "copied" + tryNumber + ".so";
162 File privateCopyFile = privateFile(privateCopyName);
163 mExpectedNameHash = hashOf(privateCopyName);
164 mExpectedContentHash = copyAndHashResource(
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000165 libraryPath("DynamicCodeLoggerNativeTestLibrary.so"), privateCopyFile);
Alan Stokese9d33142018-12-19 09:31:21 +0000166
Alan Stokes206a69d2019-02-04 15:32:12 +0000167 System.load(privateCopyFile.toString());
168 }
169 }.runTest();
Alan Stokesbe9ec972018-12-10 14:07:47 +0000170 }
171
Alan Stokese9d33142018-12-19 09:31:21 +0000172 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000173 public void testGeneratesEvents_nativeLibrary_escapedName() throws Exception {
Alan Stokes206a69d2019-02-04 15:32:12 +0000174 new TestNativeCodeWithRetries() {
175 @Override
176 protected void loadNativeCode(int tryNumber) throws Exception {
177 // A file name with a space will be escaped in the audit log; verify we un-escape it
178 // correctly.
179 String privateCopyName = "second copy " + tryNumber + ".so";
180 File privateCopyFile = privateFile(privateCopyName);
181 mExpectedNameHash = hashOf(privateCopyName);
182 mExpectedContentHash = copyAndHashResource(
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000183 libraryPath("DynamicCodeLoggerNativeTestLibrary.so"), privateCopyFile);
Alan Stokese9d33142018-12-19 09:31:21 +0000184
Alan Stokes206a69d2019-02-04 15:32:12 +0000185 System.load(privateCopyFile.toString());
186 }
187 }.runTest();
Alan Stokese9d33142018-12-19 09:31:21 +0000188 }
189
190 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000191 public void testGeneratesEvents_nativeExecutable() throws Exception {
Alan Stokes206a69d2019-02-04 15:32:12 +0000192 new TestNativeCodeWithRetries() {
193 @Override
194 protected void loadNativeCode(int tryNumber) throws Exception {
195 String privateCopyName = "test_executable" + tryNumber;
196 File privateCopyFile = privateFile(privateCopyName);
197 mExpectedNameHash = hashOf(privateCopyName);
198 mExpectedContentHash = copyAndHashResource(
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000199 "/DynamicCodeLoggerNativeExecutable", privateCopyFile);
Alan Stokes206a69d2019-02-04 15:32:12 +0000200 assertThat(privateCopyFile.setExecutable(true)).isTrue();
Alan Stokese9d33142018-12-19 09:31:21 +0000201
Alan Stokes206a69d2019-02-04 15:32:12 +0000202 Process process = Runtime.getRuntime().exec(privateCopyFile.toString());
203 int exitCode = process.waitFor();
204 assertThat(exitCode).isEqualTo(0);
205 }
206 }.runTest();
Alan Stokese9d33142018-12-19 09:31:21 +0000207 }
208
209 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000210 public void testGeneratesEvents_spoofed_validFile() throws Exception {
Alan Stokese9d33142018-12-19 09:31:21 +0000211 File privateCopyFile = privateFile("spoofed");
212
Alan Stokes206a69d2019-02-04 15:32:12 +0000213 String expectedContentHash = copyAndHashResource(
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000214 "/DynamicCodeLoggerNativeExecutable", privateCopyFile);
Alan Stokese9d33142018-12-19 09:31:21 +0000215
216 EventLog.writeEvent(EventLog.getTagCode("auditd"),
217 "type=1400 avc: granted { execute_no_trans } "
218 + "path=\"" + privateCopyFile + "\" "
219 + "scontext=u:r:untrusted_app_27: "
220 + "tcontext=u:object_r:app_data_file: "
221 + "tclass=file ");
222
223 String expectedNameHash =
224 "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3";
225
226 // Run the job to scan generated audit log entries
227 runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
228
229 // And then make sure we log events about it
230 long previousEventNanos = mostRecentEventTimeNanos();
231 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
232
233 assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
234 expectedNameHash, expectedContentHash);
235 }
236
237 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000238 public void testGeneratesEvents_spoofed_pathTraversal() throws Exception {
Alan Stokese9d33142018-12-19 09:31:21 +0000239 File privateDir = privateFile("x").getParentFile();
240
241 // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root
242 File pathTraversalToRoot = privateDir;
243 File root = new File("/");
244 while (!privateDir.equals(root)) {
245 pathTraversalToRoot = new File(pathTraversalToRoot, "..");
246 privateDir = privateDir.getParentFile();
247 }
248
249 File spoofedFile = new File(pathTraversalToRoot, "dev/urandom");
250
251 assertWithMessage("Expected " + spoofedFile + " to be readable")
252 .that(spoofedFile.canRead()).isTrue();
253
254 EventLog.writeEvent(EventLog.getTagCode("auditd"),
255 "type=1400 avc: granted { execute_no_trans } "
256 + "path=\"" + spoofedFile + "\" "
257 + "scontext=u:r:untrusted_app_27: "
258 + "tcontext=u:object_r:app_data_file: "
259 + "tclass=file ");
260
261 String expectedNameHash =
262 "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225";
263
264 // Run the job to scan generated audit log entries
265 runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
266
267 // And then trigger generating DCL events
268 long previousEventNanos = mostRecentEventTimeNanos();
269 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
270
271 assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
272 }
273
274 @Test
Alan Stokes96efc0282019-02-05 15:10:36 +0000275 public void testGeneratesEvents_spoofed_otherAppFile() throws Exception {
Alan Stokese9d33142018-12-19 09:31:21 +0000276 File ourPath = sContext.getDatabasePath("android_pay");
277 File targetPath = new File(ourPath.toString()
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000278 .replace("com.android.frameworks.dynamiccodeloggertest", "com.google.android.gms"));
Alan Stokese9d33142018-12-19 09:31:21 +0000279
280 assertWithMessage("Expected " + targetPath + " to not be readable")
281 .that(targetPath.canRead()).isFalse();
282
283 EventLog.writeEvent(EventLog.getTagCode("auditd"),
284 "type=1400 avc: granted { execute_no_trans } "
285 + "path=\"" + targetPath + "\" "
286 + "scontext=u:r:untrusted_app_27: "
287 + "tcontext=u:object_r:app_data_file: "
288 + "tclass=file ");
289
290 String expectedNameHash =
291 "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6";
292
293 // Run the job to scan generated audit log entries
294 runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
295
296 // And then trigger generating DCL events
297 long previousEventNanos = mostRecentEventTimeNanos();
298 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
299
300 assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
301 }
302
Alan Stokes206a69d2019-02-04 15:32:12 +0000303 // Abstract out the logic for running a native code loading test multiple times if needed and
304 // leaving time for audit messages to reach the log.
305 private abstract class TestNativeCodeWithRetries {
306 String mExpectedContentHash;
307 String mExpectedNameHash;
308
309 abstract void loadNativeCode(int tryNumber) throws Exception;
310
311 final void runTest() throws Exception {
312 List<String> messages = null;
313
314 for (int i = 0; i < AUDIT_LOG_RETRIES; i++) {
315 loadNativeCode(i);
316
317 SystemClock.sleep(i * RETRY_DELAY_MS);
318
319 // Run the job to scan generated audit log entries
320 runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
321
322 // And then make sure we log events about it
323 long previousEventNanos = mostRecentEventTimeNanos();
324 runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
325
326 messages = findMatchingEvents(
327 previousEventNanos, DCL_NATIVE_SUBTAG, mExpectedNameHash);
328 if (!messages.isEmpty()) {
329 break;
330 }
331 }
332
333 assertHasDclLog(messages, mExpectedContentHash);
334 }
335 }
336
Alan Stokese9d33142018-12-19 09:31:21 +0000337 private static File privateFile(String name) {
338 return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
339 }
340
Alan Stokesa3039532019-01-25 14:07:23 +0000341 private String libraryPath(final String libraryName) {
342 // This may be deprecated. but it tells us the ABI of this process which is exactly what we
343 // want.
344 return "/lib/" + Build.CPU_ABI + "/" + libraryName;
345 }
346
Alan Stokese9d33142018-12-19 09:31:21 +0000347 private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
Alan Stokes206a69d2019-02-04 15:32:12 +0000348 MessageDigest hasher = MessageDigest.getInstance(SHA_256);
Alan Stokes761d6182017-12-19 11:48:19 +0000349
350 // Copy the jar from our Java resources to a private data directory
Alan Stokes8d3b7a42019-02-19 17:14:42 +0000351 Class<?> thisClass = DynamicCodeLoggerIntegrationTests.class;
Alan Stokese9d33142018-12-19 09:31:21 +0000352 try (InputStream input = thisClass.getResourceAsStream(resourcePath);
353 OutputStream output = new FileOutputStream(copyTo)) {
Alan Stokes761d6182017-12-19 11:48:19 +0000354 byte[] buffer = new byte[1024];
355 while (true) {
356 int numRead = input.read(buffer);
357 if (numRead < 0) {
358 break;
359 }
360 output.write(buffer, 0, numRead);
361 hasher.update(buffer, 0, numRead);
362 }
363 }
364
Alan Stokesbe9ec972018-12-10 14:07:47 +0000365 // Compute the SHA-256 of the file content so we can check that it is the same as the value
366 // we see logged.
Alan Stokes206a69d2019-02-04 15:32:12 +0000367 return toHexString(hasher);
368 }
369
370 private String hashOf(String input) throws Exception {
371 MessageDigest hasher = MessageDigest.getInstance(SHA_256);
372 hasher.update(input.getBytes());
373 return toHexString(hasher);
374 }
375
376 private static String toHexString(MessageDigest hasher) {
Alan Stokes761d6182017-12-19 11:48:19 +0000377 Formatter formatter = new Formatter();
378 for (byte b : hasher.digest()) {
379 formatter.format("%02X", b);
380 }
Alan Stokes761d6182017-12-19 11:48:19 +0000381
Alan Stokesbe9ec972018-12-10 14:07:47 +0000382 return formatter.toString();
Alan Stokes761d6182017-12-19 11:48:19 +0000383 }
384
Alan Stokese9d33142018-12-19 09:31:21 +0000385 private static void runDynamicCodeLoggingJob(int jobId) throws Exception {
386 // This forces the DynamicCodeLoggingService job to start now.
387 runCommand("cmd jobscheduler run -f android " + jobId);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000388 // Wait for the job to have run.
389 long startTime = SystemClock.elapsedRealtime();
390 while (true) {
391 String response = runCommand(
Alan Stokese9d33142018-12-19 09:31:21 +0000392 "cmd jobscheduler get-job-state android " + jobId);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000393 if (!response.contains("pending") && !response.contains("active")) {
394 break;
395 }
Alan Stokese9d33142018-12-19 09:31:21 +0000396 // Don't wait forever - if it's taken > 10s then something is very wrong.
Alan Stokesbe9ec972018-12-10 14:07:47 +0000397 if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
398 throw new AssertionError("Job has not completed: " + response);
399 }
400 SystemClock.sleep(100);
401 }
402 }
Alan Stokes761d6182017-12-19 11:48:19 +0000403
Alan Stokesbe9ec972018-12-10 14:07:47 +0000404 private static String runCommand(String command) throws Exception {
405 ByteArrayOutputStream response = new ByteArrayOutputStream();
406 byte[] buffer = new byte[1000];
407 UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
408 ParcelFileDescriptor fd = ui.executeShellCommand(command);
409 try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
410 while (true) {
411 int count = input.read(buffer);
412 if (count == -1) {
413 break;
414 }
415 response.write(buffer, 0, count);
416 }
417 }
418 return response.toString("UTF-8");
419 }
Alan Stokes761d6182017-12-19 11:48:19 +0000420
Alan Stokese9d33142018-12-19 09:31:21 +0000421 private static long mostRecentEventTimeNanos() throws Exception {
422 List<Event> events = readSnetEvents();
423 return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
424 }
425
426 private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag,
427 String expectedNameHash, String expectedContentHash) throws Exception {
428 List<String> messages =
429 findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
430
Alan Stokes206a69d2019-02-04 15:32:12 +0000431 assertHasDclLog(messages, expectedContentHash);
432 }
433
434 private static void assertHasDclLog(List<String> messages, String expectedContentHash) {
Alan Stokese9d33142018-12-19 09:31:21 +0000435 assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1);
436 assertThat(messages.get(0)).endsWith(expectedContentHash);
437 }
438
439 private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag,
440 String expectedNameHash) throws Exception {
441 List<String> messages =
442 findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
443
444 assertWithMessage("Expected no matching log entries").that(messages).isEmpty();
445 }
446
447 private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag,
448 String expectedNameHash) throws Exception {
449 List<String> messages = new ArrayList<>();
450
451 for (Event event : readSnetEvents()) {
Alan Stokes761d6182017-12-19 11:48:19 +0000452 if (event.getTimeNanos() <= previousEventNanos) {
453 continue;
454 }
Alan Stokes761d6182017-12-19 11:48:19 +0000455
Alan Stokese9d33142018-12-19 09:31:21 +0000456 Object data = event.getData();
457 if (!(data instanceof Object[])) {
Alan Stokes761d6182017-12-19 11:48:19 +0000458 continue;
459 }
Alan Stokese9d33142018-12-19 09:31:21 +0000460 Object[] fields = (Object[]) data;
461
462 // We only care about DCL events that we generated.
463 String subTag = (String) fields[0];
464 if (!expectedSubTag.equals(subTag)) {
465 continue;
466 }
467 int uid = (int) fields[1];
Alan Stokesbe9ec972018-12-10 14:07:47 +0000468 if (uid != sMyUid) {
Alan Stokes761d6182017-12-19 11:48:19 +0000469 continue;
470 }
471
Alan Stokese9d33142018-12-19 09:31:21 +0000472 String message = (String) fields[2];
Alan Stokesbe9ec972018-12-10 14:07:47 +0000473 if (!message.startsWith(expectedNameHash)) {
474 continue;
475 }
476
Alan Stokese9d33142018-12-19 09:31:21 +0000477 messages.add(message);
478 //assertThat(message).endsWith(expectedContentHash);
Alan Stokes761d6182017-12-19 11:48:19 +0000479 }
Alan Stokese9d33142018-12-19 09:31:21 +0000480 return messages;
481 }
Alan Stokes761d6182017-12-19 11:48:19 +0000482
Alan Stokese9d33142018-12-19 09:31:21 +0000483 private static List<Event> readSnetEvents() throws Exception {
484 List<Event> events = new ArrayList<>();
485 EventLog.readEvents(new int[] { SNET_TAG }, events);
486 return events;
Alan Stokesbe9ec972018-12-10 14:07:47 +0000487 }
488
489 /**
490 * A class loader that does nothing useful, but importantly doesn't extend BaseDexClassLoader.
491 */
492 private static class UnknownClassLoader extends ClassLoader {
493 UnknownClassLoader(ClassLoader parent) {
494 super(parent);
495 }
Alan Stokes761d6182017-12-19 11:48:19 +0000496 }
497}