Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / java / com / google / ipc / invalidation / ticl / android2 / TiclStateManager.java
1 /*
2  * Copyright 2011 Google Inc.
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
17 package com.google.ipc.invalidation.ticl.android2;
18
19 import com.google.ipc.invalidation.common.ObjectIdDigestUtils.Sha1DigestFunction;
20 import com.google.ipc.invalidation.external.client.SystemResources;
21 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
22 import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState;
23 import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata;
24 import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclStateWithDigest;
25 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP;
26 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP;
27 import com.google.ipc.invalidation.util.Bytes;
28 import com.google.ipc.invalidation.util.Preconditions;
29 import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException;
30 import com.google.ipc.invalidation.util.TypedUtil;
31
32 import android.content.Context;
33
34 import java.io.DataInput;
35 import java.io.DataInputStream;
36 import java.io.FileInputStream;
37 import java.io.FileNotFoundException;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.util.Random;
41
42
43 /**
44  * Class to save and restore instances of {@code InvalidationClient} to and from stable storage.
45  *
46  */
47 class TiclStateManager {
48   /** Name of the file to which Ticl state will be persisted. */
49   private static final String TICL_STATE_FILENAME = "android_ticl_service_state.bin";
50
51   /**
52    * Maximum size of a Ticl state file. Files with larger size will be ignored as invalid. We use
53    * this because we allocate an array of bytes of the same size as the Ticl file and want to
54    * avoid accidentally allocating huge arrays.
55    */
56   private static final int MAX_TICL_FILE_SIZE_BYTES = 100 * 1024; // 100 kilobytes
57
58   /** Random number generator for created Ticls. */
59   private static final Random random = new Random();
60
61   /**
62    * Restores the Ticl from persistent storage if it exists. Otherwise, returns {@code null}.
63    * @param context Android system context
64    * @param resources resources to use for the Ticl
65    */
66   static AndroidInvalidationClientImpl restoreTicl(Context context,
67       SystemResources resources) {
68     AndroidTiclState state = readTiclState(context, resources.getLogger());
69     if (state == null) {
70       return null;
71     }
72     AndroidInvalidationClientImpl ticl = new AndroidInvalidationClientImpl(context, resources,
73         random, state);
74     setSchedulerId(resources, ticl);
75     return ticl;
76   }
77
78   /** Creates a new Ticl. Persistent stroage must not exist. */
79   static void createTicl(Context context, SystemResources resources, int clientType,
80       byte[] clientName, ClientConfigP config, boolean skipStartForTest) {
81     Preconditions.checkState(!doesStateFileExist(context), "Ticl already exists");
82     AndroidInvalidationClientImpl ticl = new AndroidInvalidationClientImpl(context, resources,
83         random, clientType, clientName, config);
84     if (!skipStartForTest) {
85       // Ticls are started when created unless this should be skipped for tests; we allow tests
86       // to skip starting Ticls because many integration tests assume that Ticls will not be
87       // started when created.
88       setSchedulerId(resources, ticl);
89       ticl.start();
90     }
91     saveTicl(context, resources.getLogger(), ticl);
92   }
93
94   /**
95    * Sets the scheduling id on the scheduler in {@code resources} to {@code ticl.getSchedulingId()}.
96    */
97   private static void setSchedulerId(SystemResources resources,
98       AndroidInvalidationClientImpl ticl) {
99     AndroidInternalScheduler scheduler =
100         (AndroidInternalScheduler) resources.getInternalScheduler();
101     scheduler.setTiclId(ticl.getSchedulingId());
102   }
103
104   /**
105    * Saves a Ticl instance to persistent storage.
106    *
107    * @param context Android system context
108    * @param logger logger
109    * @param ticl the Ticl instance to save
110    */
111   static void saveTicl(Context context, Logger logger, AndroidInvalidationClientImpl ticl) {
112     FileOutputStream outputStream = null;
113     try {
114
115       // Create a protobuf with the Ticl state and a digest over it.
116       AndroidTiclStateWithDigest digestedState = createDigestedState(ticl);
117
118       // Write the protobuf to storage.
119       outputStream = openStateFileForWriting(context);
120       outputStream.write(digestedState.toByteArray());
121       outputStream.close();
122     } catch (FileNotFoundException exception) {
123       logger.warning("Could not write Ticl state: %s", exception);
124     } catch (IOException exception) {
125       logger.warning("Could not write Ticl state: %s", exception);
126     } finally {
127       try {
128         if (outputStream != null) {
129           outputStream.close();
130         }
131       } catch (IOException exception) {
132         logger.warning("Exception closing Ticl state file: %s", exception);
133       }
134     }
135   }
136
137   /**
138    * Reads and returns the Android Ticl state from persistent storage. If the state was missing
139    * or invalid, returns {@code null}.
140    */
141   static AndroidTiclState readTiclState(Context context, Logger logger) {
142     FileInputStream inputStream = null;
143     try {
144       inputStream = openStateFileForReading(context);
145       DataInput input = new DataInputStream(inputStream);
146       long fileSizeBytes = inputStream.getChannel().size();
147       if (fileSizeBytes > MAX_TICL_FILE_SIZE_BYTES) {
148         logger.warning("Ignoring too-large Ticl state file with size %s > %s",
149             fileSizeBytes, MAX_TICL_FILE_SIZE_BYTES);
150       } else {
151         // Cast to int must be safe due to the above size check.
152         byte[] fileData = new byte[(int) fileSizeBytes];
153         input.readFully(fileData);
154         AndroidTiclStateWithDigest androidState = AndroidTiclStateWithDigest.parseFrom(fileData);
155
156         // Validate the digest in the method.
157         if (isDigestValid(androidState, logger)) {
158           return Preconditions.checkNotNull(androidState.getState(),
159               "validator ensures that state is set");
160         } else {
161           logger.warning("Android Ticl state failed digest check: %s", androidState);
162         }
163       }
164     } catch (FileNotFoundException exception) {
165       logger.info("Ticl state file does not exist: %s", TICL_STATE_FILENAME);
166     } catch (ValidationException exception) {
167       logger.warning("Could not read Ticl state: %s", exception);
168     } catch (IOException exception) {
169       logger.warning("Could not read Ticl state: %s", exception);
170     } finally {
171       try {
172         if (inputStream != null) {
173           inputStream.close();
174         }
175       } catch (IOException exception) {
176         logger.warning("Exception closing Ticl state file: %s", exception);
177       }
178     }
179     return null;
180   }
181
182   /**
183    * Returns a {@link AndroidTiclStateWithDigest} containing {@code ticlState} and its computed
184    * digest.
185    */
186   private static AndroidTiclStateWithDigest createDigestedState(
187       AndroidInvalidationClientImpl ticl) {
188     Sha1DigestFunction digester = new Sha1DigestFunction();
189     ApplicationClientIdP ticlAppId = ticl.getApplicationClientIdP();
190     Metadata metadata = Metadata.create(ticlAppId.getClientType(), ticlAppId.getClientName(),
191         ticl.getSchedulingId(), ticl.getConfig());
192     AndroidTiclState state = AndroidTiclState.create(ProtocolIntents.ANDROID_PROTOCOL_VERSION_VALUE,
193         ticl.marshal(), metadata);
194     digester.update(state.toByteArray());
195     AndroidTiclStateWithDigest verifiedState =
196         AndroidTiclStateWithDigest.create(state, new Bytes(digester.getDigest()));
197     return verifiedState;
198   }
199
200   /** Returns whether the digest in {@code state} is correct. */
201   private static boolean isDigestValid(AndroidTiclStateWithDigest state, Logger logger) {
202     Sha1DigestFunction digester = new Sha1DigestFunction();
203     digester.update(state.getState().toByteArray());
204     byte[] computedDigest = digester.getDigest();
205     if (!TypedUtil.<Bytes>equals(new Bytes(computedDigest), state.getDigest())) {
206       logger.warning("Android TICL state digest mismatch; computed %s for %s",
207           computedDigest, state);
208       return false;
209     }
210     return true;
211   }
212
213   /** Opens {@link #TICL_STATE_FILENAME} for writing. */
214   private static FileOutputStream openStateFileForWriting(Context context)
215       throws FileNotFoundException {
216     return context.openFileOutput(TICL_STATE_FILENAME, Context.MODE_PRIVATE);
217   }
218
219   /** Opens {@link #TICL_STATE_FILENAME} for reading. */
220   private static FileInputStream openStateFileForReading(Context context)
221       throws FileNotFoundException {
222     return context.openFileInput(TICL_STATE_FILENAME);
223   }
224
225   /** Deletes {@link #TICL_STATE_FILENAME}. */
226   public static void deleteStateFile(Context context) {
227     context.deleteFile(TICL_STATE_FILENAME);
228   }
229
230   /** Returns whether the state file exists, for tests. */
231   static boolean doesStateFileExistForTest(Context context) {
232     return doesStateFileExist(context);
233   }
234
235   /** Returns whether the state file exists. */
236   private static boolean doesStateFileExist(Context context) {
237     return context.getFileStreamPath(TICL_STATE_FILENAME).exists();
238   }
239
240   private TiclStateManager() {
241     // Disallow instantiation.
242   }
243 }