| Index: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclStateManager.java
|
| diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclStateManager.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclStateManager.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..bf9ded011aa3475cd580fd0f42c7e8b55dc12e38
|
| --- /dev/null
|
| +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclStateManager.java
|
| @@ -0,0 +1,243 @@
|
| +/*
|
| + * Copyright 2011 Google Inc.
|
| + *
|
| + * Licensed under the Apache License, Version 2.0 (the "License");
|
| + * you may not use this file except in compliance with the License.
|
| + * You may obtain a copy of the License at
|
| + *
|
| + * http://www.apache.org/licenses/LICENSE-2.0
|
| + *
|
| + * Unless required by applicable law or agreed to in writing, software
|
| + * distributed under the License is distributed on an "AS IS" BASIS,
|
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| + * See the License for the specific language governing permissions and
|
| + * limitations under the License.
|
| + */
|
| +
|
| +package com.google.ipc.invalidation.ticl.android2;
|
| +
|
| +import com.google.ipc.invalidation.common.ObjectIdDigestUtils.Sha1DigestFunction;
|
| +import com.google.ipc.invalidation.external.client.SystemResources;
|
| +import com.google.ipc.invalidation.external.client.SystemResources.Logger;
|
| +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState;
|
| +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata;
|
| +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclStateWithDigest;
|
| +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP;
|
| +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP;
|
| +import com.google.ipc.invalidation.util.Bytes;
|
| +import com.google.ipc.invalidation.util.Preconditions;
|
| +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException;
|
| +import com.google.ipc.invalidation.util.TypedUtil;
|
| +
|
| +import android.content.Context;
|
| +
|
| +import java.io.DataInput;
|
| +import java.io.DataInputStream;
|
| +import java.io.FileInputStream;
|
| +import java.io.FileNotFoundException;
|
| +import java.io.FileOutputStream;
|
| +import java.io.IOException;
|
| +import java.util.Random;
|
| +
|
| +
|
| +/**
|
| + * Class to save and restore instances of {@code InvalidationClient} to and from stable storage.
|
| + *
|
| + */
|
| +class TiclStateManager {
|
| + /** Name of the file to which Ticl state will be persisted. */
|
| + private static final String TICL_STATE_FILENAME = "android_ticl_service_state.bin";
|
| +
|
| + /**
|
| + * Maximum size of a Ticl state file. Files with larger size will be ignored as invalid. We use
|
| + * this because we allocate an array of bytes of the same size as the Ticl file and want to
|
| + * avoid accidentally allocating huge arrays.
|
| + */
|
| + private static final int MAX_TICL_FILE_SIZE_BYTES = 100 * 1024; // 100 kilobytes
|
| +
|
| + /** Random number generator for created Ticls. */
|
| + private static final Random random = new Random();
|
| +
|
| + /**
|
| + * Restores the Ticl from persistent storage if it exists. Otherwise, returns {@code null}.
|
| + * @param context Android system context
|
| + * @param resources resources to use for the Ticl
|
| + */
|
| + static AndroidInvalidationClientImpl restoreTicl(Context context,
|
| + SystemResources resources) {
|
| + AndroidTiclState state = readTiclState(context, resources.getLogger());
|
| + if (state == null) {
|
| + return null;
|
| + }
|
| + AndroidInvalidationClientImpl ticl = new AndroidInvalidationClientImpl(context, resources,
|
| + random, state);
|
| + setSchedulerId(resources, ticl);
|
| + return ticl;
|
| + }
|
| +
|
| + /** Creates a new Ticl. Persistent stroage must not exist. */
|
| + static void createTicl(Context context, SystemResources resources, int clientType,
|
| + byte[] clientName, ClientConfigP config, boolean skipStartForTest) {
|
| + Preconditions.checkState(!doesStateFileExist(context), "Ticl already exists");
|
| + AndroidInvalidationClientImpl ticl = new AndroidInvalidationClientImpl(context, resources,
|
| + random, clientType, clientName, config);
|
| + if (!skipStartForTest) {
|
| + // Ticls are started when created unless this should be skipped for tests; we allow tests
|
| + // to skip starting Ticls because many integration tests assume that Ticls will not be
|
| + // started when created.
|
| + setSchedulerId(resources, ticl);
|
| + ticl.start();
|
| + }
|
| + saveTicl(context, resources.getLogger(), ticl);
|
| + }
|
| +
|
| + /**
|
| + * Sets the scheduling id on the scheduler in {@code resources} to {@code ticl.getSchedulingId()}.
|
| + */
|
| + private static void setSchedulerId(SystemResources resources,
|
| + AndroidInvalidationClientImpl ticl) {
|
| + AndroidInternalScheduler scheduler =
|
| + (AndroidInternalScheduler) resources.getInternalScheduler();
|
| + scheduler.setTiclId(ticl.getSchedulingId());
|
| + }
|
| +
|
| + /**
|
| + * Saves a Ticl instance to persistent storage.
|
| + *
|
| + * @param context Android system context
|
| + * @param logger logger
|
| + * @param ticl the Ticl instance to save
|
| + */
|
| + static void saveTicl(Context context, Logger logger, AndroidInvalidationClientImpl ticl) {
|
| + FileOutputStream outputStream = null;
|
| + try {
|
| +
|
| + // Create a protobuf with the Ticl state and a digest over it.
|
| + AndroidTiclStateWithDigest digestedState = createDigestedState(ticl);
|
| +
|
| + // Write the protobuf to storage.
|
| + outputStream = openStateFileForWriting(context);
|
| + outputStream.write(digestedState.toByteArray());
|
| + outputStream.close();
|
| + } catch (FileNotFoundException exception) {
|
| + logger.warning("Could not write Ticl state: %s", exception);
|
| + } catch (IOException exception) {
|
| + logger.warning("Could not write Ticl state: %s", exception);
|
| + } finally {
|
| + try {
|
| + if (outputStream != null) {
|
| + outputStream.close();
|
| + }
|
| + } catch (IOException exception) {
|
| + logger.warning("Exception closing Ticl state file: %s", exception);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Reads and returns the Android Ticl state from persistent storage. If the state was missing
|
| + * or invalid, returns {@code null}.
|
| + */
|
| + static AndroidTiclState readTiclState(Context context, Logger logger) {
|
| + FileInputStream inputStream = null;
|
| + try {
|
| + inputStream = openStateFileForReading(context);
|
| + DataInput input = new DataInputStream(inputStream);
|
| + long fileSizeBytes = inputStream.getChannel().size();
|
| + if (fileSizeBytes > MAX_TICL_FILE_SIZE_BYTES) {
|
| + logger.warning("Ignoring too-large Ticl state file with size %s > %s",
|
| + fileSizeBytes, MAX_TICL_FILE_SIZE_BYTES);
|
| + } else {
|
| + // Cast to int must be safe due to the above size check.
|
| + byte[] fileData = new byte[(int) fileSizeBytes];
|
| + input.readFully(fileData);
|
| + AndroidTiclStateWithDigest androidState = AndroidTiclStateWithDigest.parseFrom(fileData);
|
| +
|
| + // Validate the digest in the method.
|
| + if (isDigestValid(androidState, logger)) {
|
| + return Preconditions.checkNotNull(androidState.getState(),
|
| + "validator ensures that state is set");
|
| + } else {
|
| + logger.warning("Android Ticl state failed digest check: %s", androidState);
|
| + }
|
| + }
|
| + } catch (FileNotFoundException exception) {
|
| + logger.info("Ticl state file does not exist: %s", TICL_STATE_FILENAME);
|
| + } catch (ValidationException exception) {
|
| + logger.warning("Could not read Ticl state: %s", exception);
|
| + } catch (IOException exception) {
|
| + logger.warning("Could not read Ticl state: %s", exception);
|
| + } finally {
|
| + try {
|
| + if (inputStream != null) {
|
| + inputStream.close();
|
| + }
|
| + } catch (IOException exception) {
|
| + logger.warning("Exception closing Ticl state file: %s", exception);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Returns a {@link AndroidTiclStateWithDigest} containing {@code ticlState} and its computed
|
| + * digest.
|
| + */
|
| + private static AndroidTiclStateWithDigest createDigestedState(
|
| + AndroidInvalidationClientImpl ticl) {
|
| + Sha1DigestFunction digester = new Sha1DigestFunction();
|
| + ApplicationClientIdP ticlAppId = ticl.getApplicationClientIdP();
|
| + Metadata metadata = Metadata.create(ticlAppId.getClientType(), ticlAppId.getClientName(),
|
| + ticl.getSchedulingId(), ticl.getConfig());
|
| + AndroidTiclState state = AndroidTiclState.create(ProtocolIntents.ANDROID_PROTOCOL_VERSION_VALUE,
|
| + ticl.marshal(), metadata);
|
| + digester.update(state.toByteArray());
|
| + AndroidTiclStateWithDigest verifiedState =
|
| + AndroidTiclStateWithDigest.create(state, new Bytes(digester.getDigest()));
|
| + return verifiedState;
|
| + }
|
| +
|
| + /** Returns whether the digest in {@code state} is correct. */
|
| + private static boolean isDigestValid(AndroidTiclStateWithDigest state, Logger logger) {
|
| + Sha1DigestFunction digester = new Sha1DigestFunction();
|
| + digester.update(state.getState().toByteArray());
|
| + byte[] computedDigest = digester.getDigest();
|
| + if (!TypedUtil.<Bytes>equals(new Bytes(computedDigest), state.getDigest())) {
|
| + logger.warning("Android TICL state digest mismatch; computed %s for %s",
|
| + computedDigest, state);
|
| + return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /** Opens {@link #TICL_STATE_FILENAME} for writing. */
|
| + private static FileOutputStream openStateFileForWriting(Context context)
|
| + throws FileNotFoundException {
|
| + return context.openFileOutput(TICL_STATE_FILENAME, Context.MODE_PRIVATE);
|
| + }
|
| +
|
| + /** Opens {@link #TICL_STATE_FILENAME} for reading. */
|
| + private static FileInputStream openStateFileForReading(Context context)
|
| + throws FileNotFoundException {
|
| + return context.openFileInput(TICL_STATE_FILENAME);
|
| + }
|
| +
|
| + /** Deletes {@link #TICL_STATE_FILENAME}. */
|
| + public static void deleteStateFile(Context context) {
|
| + context.deleteFile(TICL_STATE_FILENAME);
|
| + }
|
| +
|
| + /** Returns whether the state file exists, for tests. */
|
| + static boolean doesStateFileExistForTest(Context context) {
|
| + return doesStateFileExist(context);
|
| + }
|
| +
|
| + /** Returns whether the state file exists. */
|
| + private static boolean doesStateFileExist(Context context) {
|
| + return context.getFileStreamPath(TICL_STATE_FILENAME).exists();
|
| + }
|
| +
|
| + private TiclStateManager() {
|
| + // Disallow instantiation.
|
| + }
|
| +}
|
|
|