From ae659ace5654f40e190b3ee7f61526207b42c4cf Mon Sep 17 00:00:00 2001 From: Bessie Jiang Date: Fri, 7 Feb 2025 11:01:43 +0000 Subject: [PATCH] Add logging of the log list timestamp This CL adds the parsed log list timestamp from the JSON log list, as well as logging a SUCCESS event (with timestamp and signature) when the log list has been successfully updated. Fixes: 391327942 Fixes: 394278886 Bug: 378626065 Flag: com.android.net.ct.flags.certificate_transparency_service Test: atest NetworkSecurityUnitTests Change-Id: I8f0d96ca96cd89dce5da588cb478f9332537df48 --- .../ct/CertificateTransparencyDownloader.java | 27 ++----- .../net/ct/CertificateTransparencyLogger.java | 2 + .../ct/CertificateTransparencyLoggerImpl.java | 33 ++++++-- .../server/net/ct/CompatibilityVersion.java | 21 +++-- .../server/net/ct/LogListUpdateStatus.java | 9 +++ ...CertificateTransparencyDownloaderTest.java | 25 +++++- .../net/ct/CompatibilityVersionTest.java | 77 +++++++++++++++++-- 7 files changed, 153 insertions(+), 41 deletions(-) diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java index 1fbb3f3d43..fb42c03de7 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java @@ -199,7 +199,6 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { } LogListUpdateStatus updateStatus = mSignatureVerifier.verify(contentUri, metadataUri); - // TODO(b/391327942): parse file and log the timestamp of the log list if (!updateStatus.isSignatureVerified()) { Log.w(TAG, "Log list did not pass verification"); @@ -209,42 +208,30 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { return; } - boolean success = false; - try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) { - success = compatVersion.install(inputStream); + updateStatus = compatVersion.install(inputStream, updateStatus.toBuilder()); } catch (IOException e) { Log.e(TAG, "Could not install new content", e); return; } - if (success) { - // Reset the number of consecutive log list failure updates back to zero. - mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0); - mDataStore.store(); - } else { - mLogger.logCTLogListUpdateStateChangedEvent( - updateStatus - .toBuilder() - .setState(CTLogListUpdateState.VERSION_ALREADY_EXISTS) - .build()); - } - } + mLogger.logCTLogListUpdateStateChangedEvent(updateStatus); + } private void handleDownloadFailed(DownloadStatus status) { Log.e(TAG, "Download failed with " + status); - LogListUpdateStatus.Builder updateStatus = LogListUpdateStatus.builder(); + LogListUpdateStatus.Builder updateStatusBuilder = LogListUpdateStatus.builder(); if (status.isHttpError()) { - updateStatus + updateStatusBuilder .setState(CTLogListUpdateState.HTTP_ERROR) .setHttpErrorStatusCode(status.reason()); } else { // TODO(b/384935059): handle blocked domain logging - updateStatus.setDownloadStatus(Optional.of(status.reason())); + updateStatusBuilder.setDownloadStatus(Optional.of(status.reason())); } - mLogger.logCTLogListUpdateStateChangedEvent(updateStatus.build()); + mLogger.logCTLogListUpdateStateChangedEvent(updateStatusBuilder.build()); } private long download(String url) { diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java index 967a04b924..2a37d8f778 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java @@ -35,10 +35,12 @@ public interface CertificateTransparencyLogger { enum CTLogListUpdateState { UNKNOWN_STATE, HTTP_ERROR, + LOG_LIST_INVALID, PUBLIC_KEY_NOT_FOUND, SIGNATURE_INVALID, SIGNATURE_NOT_FOUND, SIGNATURE_VERIFICATION_FAILED, + SUCCESS, VERSION_ALREADY_EXISTS } } \ No newline at end of file diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java index 9c3210dfa4..f617523f71 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java @@ -20,6 +20,7 @@ import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFIC import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_DEVICE_OFFLINE; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_DOWNLOAD_CANNOT_RESUME; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_HTTP_ERROR; +import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_LOG_LIST_INVALID; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_NO_DISK_SPACE; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_INVALID; @@ -29,6 +30,7 @@ import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFIC import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_UNKNOWN; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_VERSION_ALREADY_EXISTS; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__PENDING_WAITING_FOR_WIFI; +import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__SUCCESS; import android.app.DownloadManager; @@ -43,6 +45,12 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger @Override public void logCTLogListUpdateStateChangedEvent(LogListUpdateStatus updateStatus) { + if (updateStatus.isSuccessful()) { + resetFailureCount(); + } else { + updateFailureCount(); + } + int updateState = updateStatus .downloadStatus() @@ -56,20 +64,31 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger updateState, failureCount, updateStatus.httpErrorStatusCode(), - updateStatus.signature()); + updateStatus.signature(), + updateStatus.logListTimestamp()); } private void logCTLogListUpdateStateChangedEvent( - int updateState, int failureCount, int httpErrorStatusCode, String signature) { - updateFailureCount(); - + int updateState, + int failureCount, + int httpErrorStatusCode, + String signature, + long logListTimestamp) { CertificateTransparencyStatsLog.write( CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED, updateState, failureCount, httpErrorStatusCode, signature, - /* logListTimestampMs= */ 0); + logListTimestamp); + } + + /** + * Resets the number of consecutive log list update failures in the data store back to zero. + */ + private void resetFailureCount() { + mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0); + mDataStore.store(); } /** @@ -112,6 +131,8 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger switch (updateState) { case HTTP_ERROR: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_HTTP_ERROR; + case LOG_LIST_INVALID: + return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_LOG_LIST_INVALID; case PUBLIC_KEY_NOT_FOUND: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_FOUND; case SIGNATURE_INVALID: @@ -120,6 +141,8 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_NOT_FOUND; case SIGNATURE_VERIFICATION_FAILED: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_VERIFICATION; + case SUCCESS: + return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__SUCCESS; case VERSION_ALREADY_EXISTS: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_VERSION_ALREADY_EXISTS; case UNKNOWN_STATE: diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java index 9d60163de1..e8a6e6433d 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java +++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java @@ -23,6 +23,8 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState; + import org.json.JSONException; import org.json.JSONObject; @@ -69,22 +71,29 @@ class CompatibilityVersion { * Installs a log list within this compatibility version directory. * * @param newContent an input stream providing the log list + * @param statusBuilder status obj builder containing details of the log list update process * @return true if the log list was installed successfully, false otherwise. * @throws IOException if the list cannot be saved in the CT directory. */ - boolean install(InputStream newContent) throws IOException { + LogListUpdateStatus install( + InputStream newContent, LogListUpdateStatus.Builder statusBuilder) throws IOException { String content = new String(newContent.readAllBytes(), UTF_8); try { + JSONObject contentJson = new JSONObject(content); return install( new ByteArrayInputStream(content.getBytes()), - new JSONObject(content).getString("version")); + contentJson.getString("version"), + statusBuilder.setLogListTimestamp(contentJson.getLong("log_list_timestamp"))); } catch (JSONException e) { Log.e(TAG, "invalid log list format", e); - return false; + + return statusBuilder.setState(CTLogListUpdateState.LOG_LIST_INVALID).build(); } } - private boolean install(InputStream newContent, String version) throws IOException { + LogListUpdateStatus install( + InputStream newContent, String version, LogListUpdateStatus.Builder statusBuilder) + throws IOException { // To support atomically replacing the old configuration directory with the new // there's a bunch of steps. We create a new directory with the logs and then do // an atomic update of the current symlink to point to the new directory. @@ -100,7 +109,7 @@ class CompatibilityVersion { if (newLogsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) { Log.i(TAG, newLogsDir + " already exists, skipping install."); deleteOldLogDirectories(); - return false; + return statusBuilder.setState(CTLogListUpdateState.VERSION_ALREADY_EXISTS).build(); } // If the symlink has not been updated then the previous installation failed and // this is a re-attempt. Clean-up leftover files and try again. @@ -134,7 +143,7 @@ class CompatibilityVersion { // 7. Cleanup Log.i(TAG, "New logs installed at " + newLogsDir); deleteOldLogDirectories(); - return true; + return statusBuilder.setState(CTLogListUpdateState.SUCCESS).build(); } String getCompatVersion() { diff --git a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java index 3d05857370..3f9b762b4d 100644 --- a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java +++ b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java @@ -19,6 +19,7 @@ import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListU import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SUCCESS; import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState; @@ -52,6 +53,14 @@ public abstract class LogListUpdateStatus { return signature() != null && signature().length() > 0; } + boolean isSuccessful() { + return state() == SUCCESS; + } + + static LogListUpdateStatus getDefaultInstance() { + return builder().build(); + } + @AutoValue.Builder abstract static class Builder { abstract Builder setState(CTLogListUpdateState updateState); diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java index 08704d1142..5443298f6a 100644 --- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java +++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java @@ -84,6 +84,7 @@ public class CertificateTransparencyDownloaderTest { private CertificateTransparencyDownloader mCertificateTransparencyDownloader; private long mNextDownloadId = 666; + private static final long LOG_LIST_TIMESTAMP = 123456789L; @Before public void setUp() throws IOException, GeneralSecurityException { @@ -426,7 +427,7 @@ public class CertificateTransparencyDownloaderTest { verify(mLogger, times(1)) .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture()); LogListUpdateStatus statusValue = mUpdateStatusCaptor.getValue(); - assertThat(statusValue.state()).isEqualTo(CTLogListUpdateState.VERSION_ALREADY_EXISTS); + assertThat(statusValue.state()).isEqualTo(CTLogListUpdateState.LOG_LIST_INVALID); assertThat(statusValue.signature()) .isEqualTo(new String(signatureBytes, StandardCharsets.UTF_8)); } @@ -466,7 +467,8 @@ public class CertificateTransparencyDownloaderTest { } @Test - public void testDownloader_endToEndSuccess_installNewVersion() throws Exception { + public void testDownloader_endToEndSuccess_installNewVersion_andLogsSuccess() throws Exception { + // Arrange String newVersion = "456"; File logListFile = makeLogListFile(newVersion); File metadataFile = sign(logListFile); @@ -474,6 +476,7 @@ public class CertificateTransparencyDownloaderTest { assertNoVersionIsInstalled(); + // Act // 1. Start download of public key. mCertificateTransparencyDownloader.startPublicKeyDownload(); @@ -491,7 +494,19 @@ public class CertificateTransparencyDownloaderTest { mCertificateTransparencyDownloader.onReceive( mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile)); + // Assert assertInstallSuccessful(newVersion); + verify(mLogger, times(1)) + .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture()); + + LogListUpdateStatus statusValue = mUpdateStatusCaptor.getValue(); + assertThat(statusValue.state()).isEqualTo(CTLogListUpdateState.SUCCESS); + assertThat(statusValue.signature()) + .isEqualTo( + new String( + Base64.getDecoder().decode(toByteArray(metadataFile)), + StandardCharsets.UTF_8)); + assertThat(statusValue.logListTimestamp()).isEqualTo(LOG_LIST_TIMESTAMP); } private void assertNoVersionIsInstalled() { @@ -600,7 +615,11 @@ public class CertificateTransparencyDownloaderTest { File logListFile = File.createTempFile("log_list", "json"); try (OutputStream outputStream = new FileOutputStream(logListFile)) { - outputStream.write(new JSONObject().put("version", version).toString().getBytes(UTF_8)); + JSONObject contentJson = + new JSONObject() + .put("version", version) + .put("log_list_timestamp", LOG_LIST_TIMESTAMP); + outputStream.write(contentJson.toString().getBytes(UTF_8)); } return logListFile; diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java index 38fff48bca..2b8b3cd4ca 100644 --- a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java +++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java @@ -15,6 +15,11 @@ */ package com.android.server.net.ct; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.LOG_LIST_INVALID; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SUCCESS; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.UNKNOWN_STATE; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.VERSION_ALREADY_EXISTS; + import static com.google.common.truth.Truth.assertThat; import androidx.test.platform.app.InstrumentationRegistry; @@ -37,6 +42,8 @@ import java.io.InputStream; public class CompatibilityVersionTest { private static final String TEST_VERSION = "v123"; + private static final long LOG_LIST_TIMESTAMP = 123456789L; + private static final String SIGNATURE = "fake_signature"; private final File mTestDir = InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(); @@ -52,6 +59,7 @@ public class CompatibilityVersionTest { @Test public void testCompatibilityVersion_versionDirectory_setupSuccessful() { File versionDir = mCompatVersion.getVersionDir(); + assertThat(versionDir.exists()).isFalse(); assertThat(versionDir.getAbsolutePath()).startsWith(mTestDir.getAbsolutePath()); assertThat(versionDir.getAbsolutePath()).endsWith(TEST_VERSION); @@ -60,6 +68,7 @@ public class CompatibilityVersionTest { @Test public void testCompatibilityVersion_symlink_setupSuccessful() { File dirSymlink = mCompatVersion.getLogsDirSymlink(); + assertThat(dirSymlink.exists()).isFalse(); assertThat(dirSymlink.getAbsolutePath()) .startsWith(mCompatVersion.getVersionDir().getAbsolutePath()); @@ -68,18 +77,44 @@ public class CompatibilityVersionTest { @Test public void testCompatibilityVersion_logsFile_setupSuccessful() { File logsFile = mCompatVersion.getLogsFile(); + assertThat(logsFile.exists()).isFalse(); assertThat(logsFile.getAbsolutePath()) .startsWith(mCompatVersion.getLogsDirSymlink().getAbsolutePath()); } + @Test + public void testCompatibilityVersion_installSuccessful_keepsStatusDetails() throws Exception { + String version = "i_am_version"; + JSONObject logList = makeLogList(version, "i_am_content"); + + try (InputStream inputStream = asStream(logList)) { + assertThat( + mCompatVersion.install( + inputStream, + LogListUpdateStatus.builder() + .setSignature(SIGNATURE) + .setState(UNKNOWN_STATE))) + .isEqualTo( + LogListUpdateStatus.builder() + .setSignature(SIGNATURE) + .setLogListTimestamp(LOG_LIST_TIMESTAMP) + // Ensure the state is correctly overridden to SUCCESS + .setState(SUCCESS) + .build()); + } + } + @Test public void testCompatibilityVersion_installSuccessful() throws Exception { String version = "i_am_version"; JSONObject logList = makeLogList(version, "i_am_content"); try (InputStream inputStream = asStream(logList)) { - assertThat(mCompatVersion.install(inputStream)).isTrue(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo(getSuccessfulUpdateStatus()); } File logListFile = mCompatVersion.getLogsFile(); @@ -107,7 +142,10 @@ public class CompatibilityVersionTest { @Test public void testCompatibilityVersion_deleteSuccessfully() throws Exception { try (InputStream inputStream = asStream(makeLogList(/* version= */ "123"))) { - assertThat(mCompatVersion.install(inputStream)).isTrue(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo(getSuccessfulUpdateStatus()); } mCompatVersion.delete(); @@ -118,7 +156,10 @@ public class CompatibilityVersionTest { @Test public void testCompatibilityVersion_invalidLogList() throws Exception { try (InputStream inputStream = new ByteArrayInputStream(("not_a_valid_list".getBytes()))) { - assertThat(mCompatVersion.install(inputStream)).isFalse(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo(LogListUpdateStatus.builder().setState(LOG_LIST_INVALID).build()); } assertThat(mCompatVersion.getLogsFile().exists()).isFalse(); @@ -138,7 +179,10 @@ public class CompatibilityVersionTest { JSONObject newLogList = makeLogList(existingVersion, "i_am_the_real_content"); try (InputStream inputStream = asStream(newLogList)) { - assertThat(mCompatVersion.install(inputStream)).isTrue(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo(getSuccessfulUpdateStatus()); } assertThat(readAsString(logsListFile)).isEqualTo(newLogList.toString()); @@ -149,11 +193,21 @@ public class CompatibilityVersionTest { String existingVersion = "666"; JSONObject existingLogList = makeLogList(existingVersion, "i_was_installed_successfully"); try (InputStream inputStream = asStream(existingLogList)) { - assertThat(mCompatVersion.install(inputStream)).isTrue(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo(getSuccessfulUpdateStatus()); } try (InputStream inputStream = asStream(makeLogList(existingVersion, "i_am_ignored"))) { - assertThat(mCompatVersion.install(inputStream)).isFalse(); + assertThat( + mCompatVersion.install( + inputStream, LogListUpdateStatus.builder())) + .isEqualTo( + LogListUpdateStatus.builder() + .setState(VERSION_ALREADY_EXISTS) + .setLogListTimestamp(LOG_LIST_TIMESTAMP) + .build()); } assertThat(readAsString(mCompatVersion.getLogsFile())) @@ -165,13 +219,22 @@ public class CompatibilityVersionTest { } private static JSONObject makeLogList(String version) throws JSONException { - return new JSONObject().put("version", version); + return new JSONObject() + .put("version", version) + .put("log_list_timestamp", LOG_LIST_TIMESTAMP); } private static JSONObject makeLogList(String version, String content) throws JSONException { return makeLogList(version).put("content", content); } + private static LogListUpdateStatus getSuccessfulUpdateStatus() { + return LogListUpdateStatus.builder() + .setState(SUCCESS) + .setLogListTimestamp(LOG_LIST_TIMESTAMP) + .build(); + } + private static String readAsString(File file) throws IOException { try (InputStream in = new FileInputStream(file)) { return new String(in.readAllBytes());